Merge "Restriction changes of #simulateDataStall"
diff --git a/Cronet/tests/common/Android.bp b/Cronet/tests/common/Android.bp
index f8bdb08..939a81c 100644
--- a/Cronet/tests/common/Android.bp
+++ b/Cronet/tests/common/Android.bp
@@ -27,7 +27,9 @@
 android_test {
     name: "NetHttpCoverageTests",
     defaults: ["CronetTestJavaDefaults"],
+    enforce_default_target_sdk_version: true,
     sdk_version: "test_current",
+    min_sdk_version: "30",
     test_suites: ["general-tests", "mts-tethering"],
     static_libs: [
         "modules-utils-native-coverage-listener",
diff --git a/Cronet/tests/common/AndroidManifest.xml b/Cronet/tests/common/AndroidManifest.xml
index efe880c..b00fc90 100644
--- a/Cronet/tests/common/AndroidManifest.xml
+++ b/Cronet/tests/common/AndroidManifest.xml
@@ -18,6 +18,7 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           xmlns:tools="http://schemas.android.com/tools"
           package="com.android.net.http.tests.coverage">
+
     <!-- NetHttpCoverageTests combines CtsNetHttpTestCases and NetHttpTests targets,
      so permissions and others are declared in their respective manifests -->
     <application tools:replace="android:label"
diff --git a/Cronet/tests/common/AndroidTest.xml b/Cronet/tests/common/AndroidTest.xml
index ca298dd..2ac418f 100644
--- a/Cronet/tests/common/AndroidTest.xml
+++ b/Cronet/tests/common/AndroidTest.xml
@@ -20,8 +20,11 @@
     </target_preparer>
     <option name="test-tag" value="NetHttpCoverageTests" />
     <!-- Tethering/Connectivity is a SDK 30+ module -->
+    <!-- TODO Switch back to Sdk30 when b/270049141 is fixed -->
     <object type="module_controller"
-            class="com.android.tradefed.testtype.suite.module.Sdk30ModuleController" />
+            class="com.android.tradefed.testtype.suite.module.Sdk31ModuleController" />
+    <option name="config-descriptor:metadata" key="mainline-param"
+            value="CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex" />
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="com.android.net.http.tests.coverage" />
         <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
diff --git a/Cronet/tests/cts/Android.bp b/Cronet/tests/cts/Android.bp
index d260694..7b440cd 100644
--- a/Cronet/tests/cts/Android.bp
+++ b/Cronet/tests/cts/Android.bp
@@ -29,6 +29,10 @@
 java_defaults {
     name: "CronetTestJavaDefaultsEnabled",
     enabled: true,
+    // TODO(danstahr): move to unconditional static_libs once the T branch is abandoned
+    static_libs: [
+        "truth",
+    ],
 }
 
 java_defaults {
@@ -43,29 +47,32 @@
 
 android_library {
     name: "CtsNetHttpTestsLib",
+    defaults: [
+        "cts_defaults",
+        "CronetTestJavaDefaults",
+    ],
     sdk_version: "test_current",
+    min_sdk_version: "30",
     srcs: [
         "src/**/*.java",
         "src/**/*.kt",
     ],
     static_libs: [
         "androidx.test.ext.junit",
-        "androidx.test.rules",
-        "androidx.core_core",
         "ctstestrunner-axt",
         "ctstestserver",
         "junit",
         "hamcrest-library",
         "kotlin-test",
+        "mockito-target",
     ],
     libs: [
-        "android.test.runner",
         "android.test.base",
-        "android.test.mock",
         "androidx.annotation_annotation",
         "framework-tethering",
         "org.apache.http.legacy",
     ],
+    lint: { test: true }
 }
 
 android_test {
diff --git a/Cronet/tests/cts/src/android/net/http/cts/BidirectionalStreamTest.kt b/Cronet/tests/cts/src/android/net/http/cts/BidirectionalStreamTest.kt
new file mode 100644
index 0000000..bead1f8
--- /dev/null
+++ b/Cronet/tests/cts/src/android/net/http/cts/BidirectionalStreamTest.kt
@@ -0,0 +1,81 @@
+/*
+ * 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.content.Context
+import android.net.http.BidirectionalStream
+import android.net.http.HttpEngine
+import android.net.http.cts.util.TestBidirectionalStreamCallback
+import android.net.http.cts.util.TestBidirectionalStreamCallback.ResponseStep
+import android.net.http.cts.util.assumeOKStatusCode
+import android.net.http.cts.util.skipIfNoInternetConnection
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import org.hamcrest.MatcherAssert
+import org.hamcrest.Matchers
+import org.junit.After
+import org.junit.Before
+import org.junit.runner.RunWith
+
+private const val URL = "https://source.android.com"
+
+/**
+ * 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.
+ */
+@RunWith(AndroidJUnit4::class)
+class BidirectionalStreamTest {
+    private val context: Context = ApplicationProvider.getApplicationContext()
+    private val callback = TestBidirectionalStreamCallback()
+    private val httpEngine = HttpEngine.Builder(context).build()
+    private var stream: BidirectionalStream? = null
+
+    @Before
+    fun setUp() {
+        skipIfNoInternetConnection(context)
+    }
+
+    @After
+    @Throws(Exception::class)
+    fun tearDown() {
+        // cancel active requests to enable engine shutdown.
+        stream?.let {
+            it.cancel()
+            callback.blockForDone()
+        }
+        httpEngine.shutdown()
+    }
+
+    private fun createBidirectionalStreamBuilder(url: String): BidirectionalStream.Builder {
+        return httpEngine.newBidirectionalStreamBuilder(url, callback.executor, callback)
+    }
+
+    @Test
+    @Throws(Exception::class)
+    fun testBidirectionalStream_GetStream_CompletesSuccessfully() {
+        stream = createBidirectionalStreamBuilder(URL).setHttpMethod("GET").build()
+        stream!!.start()
+        callback.assumeCallback(ResponseStep.ON_SUCCEEDED)
+        val info = callback.mResponseInfo
+        assumeOKStatusCode(info)
+        MatcherAssert.assertThat(
+            "Received byte count must be > 0", info.receivedByteCount, Matchers.greaterThan(0L))
+        assertEquals("h2", info.negotiatedProtocol)
+    }
+}
diff --git a/Cronet/tests/cts/src/android/net/http/cts/CallbackExceptionTest.kt b/Cronet/tests/cts/src/android/net/http/cts/CallbackExceptionTest.kt
index e17b63f..749389e 100644
--- a/Cronet/tests/cts/src/android/net/http/cts/CallbackExceptionTest.kt
+++ b/Cronet/tests/cts/src/android/net/http/cts/CallbackExceptionTest.kt
@@ -53,7 +53,7 @@
         val callback = TestUrlRequestCallback()
         callback.setFailure(FailureType.THROW_SYNC, ResponseStep.ON_RESPONSE_STARTED)
         val request = httpEngine
-            .newUrlRequestBuilder(server.successUrl, callback, callback.executor)
+            .newUrlRequestBuilder(server.successUrl, callback.executor, callback)
             .build()
 
         request.start()
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 2701ed0..77cb30e 100644
--- a/Cronet/tests/cts/src/android/net/http/cts/ConnectionMigrationOptionsTest.kt
+++ b/Cronet/tests/cts/src/android/net/http/cts/ConnectionMigrationOptionsTest.kt
@@ -17,30 +17,52 @@
 package android.net.http.cts
 
 import android.net.http.ConnectionMigrationOptions
+import android.net.http.ConnectionMigrationOptions.MIGRATION_OPTION_ENABLED
+import android.net.http.ConnectionMigrationOptions.MIGRATION_OPTION_UNSPECIFIED
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import kotlin.test.Test
-import kotlin.test.assertNotNull
-import kotlin.test.assertTrue
+import kotlin.test.assertEquals
 import org.junit.runner.RunWith
 
 @RunWith(AndroidJUnit4::class)
 class ConnectionMigrationOptionsTest {
 
     @Test
+    fun testConnectionMigrationOptions_defaultValues() {
+        val options =
+                ConnectionMigrationOptions.Builder().build()
+
+        assertEquals(MIGRATION_OPTION_UNSPECIFIED, options.allowNonDefaultNetworkUsageEnabled)
+        assertEquals(MIGRATION_OPTION_UNSPECIFIED, options.defaultNetworkMigrationEnabled)
+        assertEquals(MIGRATION_OPTION_UNSPECIFIED, options.pathDegradationMigrationEnabled)
+    }
+
+    @Test
     fun testConnectionMigrationOptions_enableDefaultNetworkMigration_returnSetValue() {
         val options =
-            ConnectionMigrationOptions.Builder().setEnableDefaultNetworkMigration(true).build()
+            ConnectionMigrationOptions.Builder()
+                    .setDefaultNetworkMigrationEnabled(MIGRATION_OPTION_ENABLED)
+                    .build()
 
-        assertNotNull(options.enableDefaultNetworkMigration)
-        assertTrue(options.enableDefaultNetworkMigration!!)
+        assertEquals(MIGRATION_OPTION_ENABLED, options.defaultNetworkMigrationEnabled)
     }
 
     @Test
     fun testConnectionMigrationOptions_enablePathDegradationMigration_returnSetValue() {
         val options =
-            ConnectionMigrationOptions.Builder().setEnablePathDegradationMigration(true).build()
+            ConnectionMigrationOptions.Builder()
+                    .setPathDegradationMigrationEnabled(MIGRATION_OPTION_ENABLED)
+                    .build()
 
-        assertNotNull(options.enablePathDegradationMigration)
-        assertTrue(options.enablePathDegradationMigration!!)
+        assertEquals(MIGRATION_OPTION_ENABLED, options.pathDegradationMigrationEnabled)
+    }
+
+    @Test
+    fun testConnectionMigrationOptions_allowNonDefaultNetworkUsage_returnSetValue() {
+        val options =
+                ConnectionMigrationOptions.Builder()
+                        .setAllowNonDefaultNetworkUsageEnabled(MIGRATION_OPTION_ENABLED).build()
+
+        assertEquals(MIGRATION_OPTION_ENABLED, options.allowNonDefaultNetworkUsageEnabled)
     }
 }
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 b96e931..8af544e 100644
--- a/Cronet/tests/cts/src/android/net/http/cts/DnsOptionsTest.kt
+++ b/Cronet/tests/cts/src/android/net/http/cts/DnsOptionsTest.kt
@@ -17,13 +17,14 @@
 package android.net.http.cts
 
 import android.net.http.DnsOptions
+import android.net.http.DnsOptions.DNS_OPTION_ENABLED
+import android.net.http.DnsOptions.DNS_OPTION_UNSPECIFIED
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import java.time.Duration
 import kotlin.test.Test
 import kotlin.test.assertEquals
 import kotlin.test.assertNotNull
 import kotlin.test.assertNull
-import kotlin.test.assertTrue
 import org.junit.runner.RunWith
 
 @RunWith(AndroidJUnit4::class)
@@ -33,20 +34,22 @@
     fun testDnsOptions_defaultValues() {
         val options = DnsOptions.Builder().build()
 
-        assertNull(options.persistHostCache)
+        assertEquals(DNS_OPTION_UNSPECIFIED, options.persistHostCacheEnabled)
         assertNull(options.persistHostCachePeriod)
-        assertNull(options.enableStaleDns)
+        assertEquals(DNS_OPTION_UNSPECIFIED, options.staleDnsEnabled)
         assertNull(options.staleDnsOptions)
-        assertNull(options.useHttpStackDnsResolver)
-        assertNull(options.preestablishConnectionsToStaleDnsResults)
+        assertEquals(DNS_OPTION_UNSPECIFIED, options.useHttpStackDnsResolverEnabled)
+        assertEquals(DNS_OPTION_UNSPECIFIED,
+                options.preestablishConnectionsToStaleDnsResultsEnabled)
     }
 
     @Test
     fun testDnsOptions_persistHostCache_returnSetValue() {
-        val options = DnsOptions.Builder().setPersistHostCache(true).build()
+        val options = DnsOptions.Builder()
+                .setPersistHostCacheEnabled(DNS_OPTION_ENABLED)
+                .build()
 
-        assertNotNull(options.persistHostCache)
-        assertTrue(options.persistHostCache!!)
+        assertEquals(DNS_OPTION_ENABLED, options.persistHostCacheEnabled)
     }
 
     @Test
@@ -59,44 +62,63 @@
 
     @Test
     fun testDnsOptions_enableStaleDns_returnSetValue() {
-        val options = DnsOptions.Builder().setEnableStaleDns(true).build()
+        val options = DnsOptions.Builder()
+                .setStaleDnsEnabled(DNS_OPTION_ENABLED)
+                .build()
 
-        assertNotNull(options.enableStaleDns)
-        assertTrue(options.enableStaleDns!!)
+        assertEquals(DNS_OPTION_ENABLED, options.staleDnsEnabled)
     }
 
     @Test
     fun testDnsOptions_useHttpStackDnsResolver_returnsSetValue() {
-        val options = DnsOptions.Builder().setUseHttpStackDnsResolver(true).build()
+        val options = DnsOptions.Builder()
+                .setUseHttpStackDnsResolverEnabled(DNS_OPTION_ENABLED)
+                .build()
 
-        assertNotNull(options.useHttpStackDnsResolver)
-        assertTrue(options.useHttpStackDnsResolver!!)
+        assertEquals(DNS_OPTION_ENABLED, options.useHttpStackDnsResolverEnabled)
     }
 
     @Test
     fun testDnsOptions_preestablishConnectionsToStaleDnsResults_returnsSetValue() {
-        val options = DnsOptions.Builder().setPreestablishConnectionsToStaleDnsResults(true).build()
+        val options = DnsOptions.Builder()
+                .setPreestablishConnectionsToStaleDnsResultsEnabled(DNS_OPTION_ENABLED)
+                .build()
 
-        assertNotNull(options.preestablishConnectionsToStaleDnsResults)
-        assertTrue(options.preestablishConnectionsToStaleDnsResults!!)
+        assertEquals(DNS_OPTION_ENABLED,
+                options.preestablishConnectionsToStaleDnsResultsEnabled)
+    }
+
+    @Test
+    fun testDnsOptions_setStaleDnsOptions_returnsSetValues() {
+        val staleOptions = DnsOptions.StaleDnsOptions.Builder()
+                .setAllowCrossNetworkUsageEnabled(DNS_OPTION_ENABLED)
+                .setFreshLookupTimeout(Duration.ofMillis(1234))
+                .build()
+        val options = DnsOptions.Builder()
+                .setStaleDnsEnabled(DNS_OPTION_ENABLED)
+                .setStaleDnsOptions(staleOptions)
+                .build()
+
+        assertEquals(DNS_OPTION_ENABLED, options.staleDnsEnabled)
+        assertEquals(staleOptions, options.staleDnsOptions)
     }
 
     @Test
     fun testStaleDnsOptions_defaultValues() {
         val options = DnsOptions.StaleDnsOptions.Builder().build()
 
-        assertNull(options.allowCrossNetworkUsage)
-        assertNull(options.freshLookupTimeoutMillis)
-        assertNull(options.maxExpiredDelayMillis)
-        assertNull(options.useStaleOnNameNotResolved)
+        assertEquals(DNS_OPTION_UNSPECIFIED, options.allowCrossNetworkUsageEnabled)
+        assertNull(options.freshLookupTimeout)
+        assertNull(options.maxExpiredDelay)
+        assertEquals(DNS_OPTION_UNSPECIFIED, options.useStaleOnNameNotResolvedEnabled)
     }
 
     @Test
     fun testStaleDnsOptions_allowCrossNetworkUsage_returnsSetValue() {
-        val options = DnsOptions.StaleDnsOptions.Builder().setAllowCrossNetworkUsage(true).build()
+        val options = DnsOptions.StaleDnsOptions.Builder()
+                .setAllowCrossNetworkUsageEnabled(DNS_OPTION_ENABLED).build()
 
-        assertNotNull(options.allowCrossNetworkUsage)
-        assertTrue(options.allowCrossNetworkUsage!!)
+        assertEquals(DNS_OPTION_ENABLED, options.allowCrossNetworkUsageEnabled)
     }
 
     @Test
@@ -104,17 +126,17 @@
         val duration = Duration.ofMillis(12345)
         val options = DnsOptions.StaleDnsOptions.Builder().setFreshLookupTimeout(duration).build()
 
-        assertNotNull(options.freshLookupTimeoutMillis)
-        assertEquals(duration.toMillis(), options.freshLookupTimeoutMillis!!)
+        assertNotNull(options.freshLookupTimeout)
+        assertEquals(duration, options.freshLookupTimeout!!)
     }
 
     @Test
     fun testStaleDnsOptions_useStaleOnNameNotResolved_returnsSetValue() {
-        val options =
-                DnsOptions.StaleDnsOptions.Builder().setUseStaleOnNameNotResolved(true).build()
+        val options = DnsOptions.StaleDnsOptions.Builder()
+                .setUseStaleOnNameNotResolvedEnabled(DNS_OPTION_ENABLED)
+                .build()
 
-        assertNotNull(options.useStaleOnNameNotResolved)
-        assertTrue(options.useStaleOnNameNotResolved!!)
+        assertEquals(DNS_OPTION_ENABLED, options.useStaleOnNameNotResolvedEnabled)
     }
 
     @Test
@@ -122,7 +144,7 @@
         val duration = Duration.ofMillis(12345)
         val options = DnsOptions.StaleDnsOptions.Builder().setMaxExpiredDelay(duration).build()
 
-        assertNotNull(options.maxExpiredDelayMillis)
-        assertEquals(duration.toMillis(), options.maxExpiredDelayMillis!!)
+        assertNotNull(options.maxExpiredDelay)
+        assertEquals(duration, options.maxExpiredDelay!!)
     }
 }
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 8663a67..0be3ea1 100644
--- a/Cronet/tests/cts/src/android/net/http/cts/HttpEngineTest.java
+++ b/Cronet/tests/cts/src/android/net/http/cts/HttpEngineTest.java
@@ -20,25 +20,33 @@
 import static android.net.http.cts.util.TestUtilsKt.assumeOKStatusCode;
 import static android.net.http.cts.util.TestUtilsKt.skipIfNoInternetConnection;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.containsString;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
 import android.content.Context;
+import android.net.Network;
+import android.net.http.ConnectionMigrationOptions;
+import android.net.http.DnsOptions;
 import android.net.http.HttpEngine;
 import android.net.http.UrlRequest;
 import android.net.http.UrlResponseInfo;
+import android.net.http.cts.util.HttpCtsTestServer;
 import android.net.http.cts.util.TestUrlRequestCallback;
 import android.net.http.cts.util.TestUrlRequestCallback.ResponseStep;
 
-import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mockito;
 
 @RunWith(AndroidJUnit4.class)
 public class HttpEngineTest {
@@ -47,15 +55,18 @@
 
     private HttpEngine.Builder mEngineBuilder;
     private TestUrlRequestCallback mCallback;
+    private HttpCtsTestServer mTestServer;
     private UrlRequest mRequest;
     private HttpEngine mEngine;
+    private Context mContext;
 
     @Before
     public void setUp() throws Exception {
-        Context context = InstrumentationRegistry.getInstrumentation().getContext();
-        skipIfNoInternetConnection(context);
-        mEngineBuilder = new HttpEngine.Builder(context);
+        mContext = ApplicationProvider.getApplicationContext();
+        skipIfNoInternetConnection(mContext);
+        mEngineBuilder = new HttpEngine.Builder(mContext);
         mCallback = new TestUrlRequestCallback();
+        mTestServer = new HttpCtsTestServer(mContext);
     }
 
     @After
@@ -67,6 +78,9 @@
         if (mEngine != null) {
             mEngine.shutdown();
         }
+        if (mTestServer != null) {
+            mTestServer.shutdown();
+        }
     }
 
     private boolean isQuic(String negotiatedProtocol) {
@@ -77,7 +91,7 @@
     public void testHttpEngine_Default() throws Exception {
         mEngine = mEngineBuilder.build();
         UrlRequest.Builder builder =
-                mEngine.newUrlRequestBuilder(URL, mCallback, mCallback.getExecutor());
+                mEngine.newUrlRequestBuilder(URL, mCallback.getExecutor(), mCallback);
         mRequest = builder.build();
         mRequest.start();
 
@@ -90,10 +104,42 @@
     }
 
     @Test
+    public void testHttpEngine_EnableHttpCache() {
+        // We need a server which sets cache-control != no-cache.
+        String url = "https://www.example.com";
+        mEngine =
+                mEngineBuilder
+                        .setStoragePath(mContext.getApplicationInfo().dataDir)
+                        .setEnableHttpCache(HttpEngine.Builder.HTTP_CACHE_DISK,
+                                            /* maxSize */ 100 * 1024)
+                        .build();
+
+        UrlRequest.Builder builder =
+                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);
+        UrlResponseInfo info = mCallback.mResponseInfo;
+        assumeOKStatusCode(info);
+        assertFalse(info.wasCached());
+
+        mCallback = new TestUrlRequestCallback();
+        builder = mEngine.newUrlRequestBuilder(url, mCallback.getExecutor(), mCallback);
+        mRequest = builder.build();
+        mRequest.start();
+        mCallback.assumeCallback(ResponseStep.ON_SUCCEEDED);
+        info = mCallback.mResponseInfo;
+        assertOKStatusCode(info);
+        assertTrue(info.wasCached());
+    }
+
+    @Test
     public void testHttpEngine_DisableHttp2() throws Exception {
         mEngine = mEngineBuilder.setEnableHttp2(false).build();
         UrlRequest.Builder builder =
-                mEngine.newUrlRequestBuilder(URL, mCallback, mCallback.getExecutor());
+                mEngine.newUrlRequestBuilder(URL, mCallback.getExecutor(), mCallback);
         mRequest = builder.build();
         mRequest.start();
 
@@ -106,6 +152,36 @@
     }
 
     @Test
+    public void testHttpEngine_EnablePublicKeyPinningBypassForLocalTrustAnchors() {
+        // 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);
+        mRequest = builder.build();
+        mRequest.start();
+        mCallback.expectCallback(ResponseStep.ON_SUCCEEDED);
+
+        mEngine.shutdown();
+        mEngine = mEngineBuilder.setEnablePublicKeyPinningBypassForLocalTrustAnchors(true).build();
+        mCallback = new TestUrlRequestCallback();
+        builder = mEngine.newUrlRequestBuilder(URL, mCallback.getExecutor(), mCallback);
+        mRequest = builder.build();
+        mRequest.start();
+        mCallback.expectCallback(ResponseStep.ON_SUCCEEDED);
+
+        // TODO(b/270918920): We should also test with a certificate not present in the device's
+        // trusted store.
+        // This requires either:
+        // * Mocking the underlying CertificateVerifier.
+        // * Or, having the server return a root certificate not present in the device's trusted
+        //   store.
+        // The former doesn't make sense for a CTS test as it would depend on the underlying
+        // implementation. The latter is something we should support once we write a proper test
+        // server.
+    }
+
+    @Test
     public void testHttpEngine_EnableQuic() throws Exception {
         mEngine = mEngineBuilder.setEnableQuic(true).addQuicHint(HOST, 443, 443).build();
         // The hint doesn't guarantee that QUIC will win the race, just that it will race TCP.
@@ -114,7 +190,7 @@
         for (int i = 0; i < 5; i++) {
             mCallback = new TestUrlRequestCallback();
             UrlRequest.Builder builder =
-                    mEngine.newUrlRequestBuilder(URL, mCallback, mCallback.getExecutor());
+                    mEngine.newUrlRequestBuilder(URL, mCallback.getExecutor(), mCallback);
             mRequest = builder.build();
             mRequest.start();
 
@@ -136,4 +212,125 @@
     public void testHttpEngine_GetDefaultUserAgent() throws Exception {
         assertThat(mEngineBuilder.getDefaultUserAgent(), containsString("AndroidHttpClient"));
     }
-}
+
+    @Test
+    public void testHttpEngine_requestUsesDefaultUserAgent() throws Exception {
+        mEngine = mEngineBuilder.build();
+        HttpCtsTestServer server =
+                new HttpCtsTestServer(ApplicationProvider.getApplicationContext());
+
+        String url = server.getUserAgentUrl();
+        UrlRequest request =
+                mEngine.newUrlRequestBuilder(url, mCallback.getExecutor(), mCallback).build();
+        request.start();
+
+        mCallback.expectCallback(ResponseStep.ON_SUCCEEDED);
+        UrlResponseInfo info = mCallback.mResponseInfo;
+        assertOKStatusCode(info);
+        String receivedUserAgent = extractUserAgent(mCallback.mResponseAsString);
+
+        assertThat(receivedUserAgent).isEqualTo(mEngineBuilder.getDefaultUserAgent());
+    }
+
+    @Test
+    public void testHttpEngine_requestUsesCustomUserAgent() throws Exception {
+        String userAgent = "CtsTests User Agent";
+        HttpCtsTestServer server =
+                new HttpCtsTestServer(ApplicationProvider.getApplicationContext());
+        mEngine =
+                new HttpEngine.Builder(ApplicationProvider.getApplicationContext())
+                        .setUserAgent(userAgent)
+                        .build();
+
+        String url = server.getUserAgentUrl();
+        UrlRequest request =
+                mEngine.newUrlRequestBuilder(url, mCallback.getExecutor(), mCallback).build();
+        request.start();
+
+        mCallback.expectCallback(ResponseStep.ON_SUCCEEDED);
+        UrlResponseInfo info = mCallback.mResponseInfo;
+        assertOKStatusCode(info);
+        String receivedUserAgent = extractUserAgent(mCallback.mResponseAsString);
+
+        assertThat(receivedUserAgent).isEqualTo(userAgent);
+    }
+
+    private static String extractUserAgent(String userAgentResponseBody) {
+        // If someone wants to be evil and have the title HTML tag a part of the user agent,
+        // they'll have to fix this method :)
+        return userAgentResponseBody
+                .replaceFirst(".*<title>", "")
+                .replaceFirst("</title>.*", "");
+    }
+
+    @Test
+    public void testHttpEngine_bindToNetwork() throws Exception {
+        // Create a fake Android.net.Network. Since that network doesn't exist, binding to
+        // that should end up in a failed request.
+        Network mockNetwork = Mockito.mock(Network.class);
+        Mockito.when(mockNetwork.getNetworkHandle()).thenReturn(123L);
+        String url = mTestServer.getSuccessUrl();
+
+        mEngine = mEngineBuilder.build();
+        mEngine.bindToNetwork(mockNetwork);
+        UrlRequest.Builder builder =
+                mEngine.newUrlRequestBuilder(url, mCallback.getExecutor(), mCallback);
+        mRequest = builder.build();
+        mRequest.start();
+
+        mCallback.expectCallback(ResponseStep.ON_FAILED);
+    }
+
+    @Test
+    public void testHttpEngine_unbindFromNetwork() throws Exception {
+        // Create a fake Android.net.Network. Since that network doesn't exist, binding to
+        // that should end up in a failed request.
+        Network mockNetwork = Mockito.mock(Network.class);
+        Mockito.when(mockNetwork.getNetworkHandle()).thenReturn(123L);
+        String url = mTestServer.getSuccessUrl();
+
+        mEngine = mEngineBuilder.build();
+        // Bind to the fake network but then unbind. This should result in a successful
+        // request.
+        mEngine.bindToNetwork(mockNetwork);
+        mEngine.bindToNetwork(null);
+        UrlRequest.Builder builder =
+                mEngine.newUrlRequestBuilder(url, mCallback.getExecutor(), mCallback);
+        mRequest = builder.build();
+        mRequest.start();
+
+        mCallback.expectCallback(ResponseStep.ON_SUCCEEDED);
+        UrlResponseInfo info = mCallback.mResponseInfo;
+        assertOKStatusCode(info);
+    }
+
+    @Test
+    public void testHttpEngine_setConnectionMigrationOptions_requestSucceeds() {
+        ConnectionMigrationOptions options = new ConnectionMigrationOptions.Builder().build();
+        mEngine = mEngineBuilder.setConnectionMigrationOptions(options).build();
+        UrlRequest.Builder builder =
+                mEngine.newUrlRequestBuilder(
+                        mTestServer.getSuccessUrl(), mCallback.getExecutor(), mCallback);
+        mRequest = builder.build();
+        mRequest.start();
+
+        mCallback.expectCallback(ResponseStep.ON_SUCCEEDED);
+        UrlResponseInfo info = mCallback.mResponseInfo;
+        assertOKStatusCode(info);
+    }
+
+    @Test
+    public void testHttpEngine_setDnsOptions_requestSucceeds() {
+        DnsOptions options = new DnsOptions.Builder().build();
+        mEngine = mEngineBuilder.setDnsOptions(options).build();
+        UrlRequest.Builder builder =
+                mEngine.newUrlRequestBuilder(
+                        mTestServer.getSuccessUrl(), mCallback.getExecutor(), mCallback);
+        mRequest = builder.build();
+        mRequest.start();
+
+        mCallback.expectCallback(ResponseStep.ON_SUCCEEDED);
+        UrlResponseInfo info = mCallback.mResponseInfo;
+        assertOKStatusCode(info);
+    }
+}
\ No newline at end of file
diff --git a/Cronet/tests/cts/src/android/net/http/cts/NetworkExceptionTest.kt b/Cronet/tests/cts/src/android/net/http/cts/NetworkExceptionTest.kt
index a2611e4..dd4cf0d 100644
--- a/Cronet/tests/cts/src/android/net/http/cts/NetworkExceptionTest.kt
+++ b/Cronet/tests/cts/src/android/net/http/cts/NetworkExceptionTest.kt
@@ -20,12 +20,15 @@
 import android.net.http.NetworkException
 import android.net.http.cts.util.TestUrlRequestCallback
 import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import kotlin.test.assertEquals
 import kotlin.test.assertIs
 import kotlin.test.assertSame
 import kotlin.test.assertTrue
 import org.junit.Test
+import org.junit.runner.RunWith
 
+@RunWith(AndroidJUnit4::class)
 class NetworkExceptionTest {
 
     @Test
@@ -47,7 +50,7 @@
         val httpEngine = HttpEngine.Builder(ApplicationProvider.getApplicationContext()).build()
         val callback = TestUrlRequestCallback()
         val request =
-            httpEngine.newUrlRequestBuilder("http://localhost", callback, callback.executor).build()
+            httpEngine.newUrlRequestBuilder("http://localhost", callback.executor, callback).build()
 
         request.start()
         callback.blockForDone()
diff --git a/Cronet/tests/cts/src/android/net/http/cts/QuicOptionsTest.kt b/Cronet/tests/cts/src/android/net/http/cts/QuicOptionsTest.kt
new file mode 100644
index 0000000..a05aecd
--- /dev/null
+++ b/Cronet/tests/cts/src/android/net/http/cts/QuicOptionsTest.kt
@@ -0,0 +1,67 @@
+/*
+ * 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.QuicOptions
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import java.time.Duration
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class QuicOptionsTest {
+    @Test
+    fun testQuicOptions_defaultValues() {
+        val quicOptions = QuicOptions.Builder().build()
+        assertThat(quicOptions.allowedQuicHosts).isEmpty()
+        assertThat(quicOptions.handshakeUserAgent).isNull()
+        assertThat(quicOptions.idleConnectionTimeout).isNull()
+        assertThat(quicOptions.inMemoryServerConfigsCacheSize).isNull()
+    }
+
+    @Test
+    fun testQuicOptions_quicHostAllowlist_returnsAddedValues() {
+        val quicOptions = QuicOptions.Builder()
+                .addAllowedQuicHost("foo")
+                .addAllowedQuicHost("bar")
+                .addAllowedQuicHost("foo")
+                .addAllowedQuicHost("baz")
+                .build()
+        assertThat(quicOptions.allowedQuicHosts)
+                .containsExactly("foo", "bar", "baz")
+                .inOrder()
+    }
+
+    @Test
+    fun testQuicOptions_idleConnectionTimeout_returnsSetValue() {
+        val timeout = Duration.ofMinutes(10)
+        val quicOptions = QuicOptions.Builder()
+                .setIdleConnectionTimeout(timeout)
+                .build()
+        assertThat(quicOptions.idleConnectionTimeout)
+                .isEqualTo(timeout)
+    }
+
+    @Test
+    fun testQuicOptions_inMemoryServerConfigsCacheSize_returnsSetValue() {
+        val quicOptions = QuicOptions.Builder()
+                .setInMemoryServerConfigsCacheSize(42)
+                .build()
+        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 5256bae..e24121f 100644
--- a/Cronet/tests/cts/src/android/net/http/cts/UrlRequestTest.java
+++ b/Cronet/tests/cts/src/android/net/http/cts/UrlRequestTest.java
@@ -19,13 +19,21 @@
 import static android.net.http.cts.util.TestUtilsKt.assertOKStatusCode;
 import static android.net.http.cts.util.TestUtilsKt.skipIfNoInternetConnection;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.greaterThan;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
 
 import android.content.Context;
 import android.net.http.HttpEngine;
+import android.net.http.HttpException;
+import android.net.http.InlineExecutionProhibitedException;
+import android.net.http.UploadDataProvider;
+import android.net.http.UploadDataSink;
 import android.net.http.UrlRequest;
 import android.net.http.UrlRequest.Status;
 import android.net.http.UrlResponseInfo;
@@ -34,24 +42,40 @@
 import android.net.http.cts.util.TestUploadDataProvider;
 import android.net.http.cts.util.TestUrlRequestCallback;
 import android.net.http.cts.util.TestUrlRequestCallback.ResponseStep;
+import android.net.http.cts.util.UploadDataProviders;
 
-import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.google.common.base.Strings;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.net.URLEncoder;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+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;
+
 @RunWith(AndroidJUnit4.class)
 public class UrlRequestTest {
+    private static final Executor DIRECT_EXECUTOR = Runnable::run;
+
     private TestUrlRequestCallback mCallback;
     private HttpCtsTestServer mTestServer;
     private HttpEngine mHttpEngine;
 
     @Before
     public void setUp() throws Exception {
-        Context context = InstrumentationRegistry.getInstrumentation().getContext();
+        Context context = ApplicationProvider.getApplicationContext();
         skipIfNoInternetConnection(context);
         HttpEngine.Builder builder = new HttpEngine.Builder(context);
         mHttpEngine = builder.build();
@@ -70,7 +94,7 @@
     }
 
     private UrlRequest.Builder createUrlRequestBuilder(String url) {
-        return mHttpEngine.newUrlRequestBuilder(url, mCallback, mCallback.getExecutor());
+        return mHttpEngine.newUrlRequestBuilder(url, mCallback.getExecutor(), mCallback);
     }
 
     @Test
@@ -113,8 +137,9 @@
         String testData = "test";
         UrlRequest.Builder builder = createUrlRequestBuilder(mTestServer.getEchoBodyUrl());
 
-        TestUploadDataProvider dataProvider = new TestUploadDataProvider(
-                TestUploadDataProvider.SuccessCallbackMode.SYNC, mCallback.getExecutor());
+        TestUploadDataProvider dataProvider =
+                new TestUploadDataProvider(
+                        TestUploadDataProvider.SuccessCallbackMode.SYNC, mCallback.getExecutor());
         dataProvider.addRead(testData.getBytes());
         builder.setUploadDataProvider(dataProvider, mCallback.getExecutor());
         builder.addHeader("Content-Type", "text/html");
@@ -127,8 +152,251 @@
     }
 
     @Test
-    public void testUrlRequestFail_FailedCalled() throws Exception {
+    public void testUrlRequestFail_FailedCalled() {
         createUrlRequestBuilder("http://0.0.0.0:0/").build().start();
         mCallback.expectCallback(ResponseStep.ON_FAILED);
     }
+
+    @Test
+    public void testUrlRequest_directExecutor_allowed() throws InterruptedException {
+        TestUrlRequestCallback callback = new TestUrlRequestCallback();
+        callback.setAllowDirectExecutor(true);
+        UrlRequest.Builder builder = mHttpEngine.newUrlRequestBuilder(
+                mTestServer.getEchoBodyUrl(), DIRECT_EXECUTOR, callback);
+        UploadDataProvider dataProvider = InMemoryUploadDataProvider.fromUtf8String("test");
+        builder.setUploadDataProvider(dataProvider, DIRECT_EXECUTOR);
+        builder.addHeader("Content-Type", "text/plain;charset=UTF-8");
+        builder.setDirectExecutorAllowed(true);
+        builder.build().start();
+        callback.blockForDone();
+
+        if (callback.mOnErrorCalled) {
+            throw new AssertionError("Expected no exception", callback.mError);
+        }
+
+        assertEquals(200, callback.mResponseInfo.getHttpStatusCode());
+        assertEquals("test", callback.mResponseAsString);
+    }
+
+    @Test
+    public void testUrlRequest_directExecutor_disallowed_uploadDataProvider() throws Exception {
+        TestUrlRequestCallback callback = new TestUrlRequestCallback();
+        // This applies just locally to the test callback, not to SUT
+        callback.setAllowDirectExecutor(true);
+
+        UrlRequest.Builder builder = mHttpEngine.newUrlRequestBuilder(
+                mTestServer.getEchoBodyUrl(), Executors.newSingleThreadExecutor(), callback);
+        UploadDataProvider dataProvider = InMemoryUploadDataProvider.fromUtf8String("test");
+
+        builder.setUploadDataProvider(dataProvider, DIRECT_EXECUTOR)
+                .addHeader("Content-Type", "text/plain;charset=UTF-8")
+                .build()
+                .start();
+        callback.blockForDone();
+
+        assertTrue(callback.mOnErrorCalled);
+        assertTrue(callback.mError.getCause() instanceof InlineExecutionProhibitedException);
+    }
+
+    @Test
+    public void testUrlRequest_directExecutor_disallowed_responseCallback() throws Exception {
+        TestUrlRequestCallback callback = new TestUrlRequestCallback();
+        // This applies just locally to the test callback, not to SUT
+        callback.setAllowDirectExecutor(true);
+
+        UrlRequest.Builder builder = mHttpEngine.newUrlRequestBuilder(
+                mTestServer.getEchoBodyUrl(), DIRECT_EXECUTOR, callback);
+        UploadDataProvider dataProvider = InMemoryUploadDataProvider.fromUtf8String("test");
+
+        builder.setUploadDataProvider(dataProvider, Executors.newSingleThreadExecutor())
+                .addHeader("Content-Type", "text/plain;charset=UTF-8")
+                .build()
+                .start();
+        callback.blockForDone();
+
+        assertTrue(callback.mOnErrorCalled);
+        assertTrue(callback.mError.getCause() instanceof InlineExecutionProhibitedException);
+    }
+
+    @Test
+    public void testUrlRequest_nonDirectByteBuffer() throws Exception {
+        BlockingQueue<HttpException> onFailedException = new ArrayBlockingQueue<>(1);
+
+        UrlRequest request =
+                mHttpEngine
+                        .newUrlRequestBuilder(
+                                mTestServer.getSuccessUrl(),
+                                Executors.newSingleThreadExecutor(),
+                                new StubUrlRequestCallback() {
+                                    @Override
+                                    public void onResponseStarted(
+                                            UrlRequest request, UrlResponseInfo info) {
+                                        // note: allocate, not allocateDirect
+                                        request.read(ByteBuffer.allocate(1024));
+                                    }
+
+                                    @Override
+                                    public void onFailed(
+                                            UrlRequest request,
+                                            UrlResponseInfo info,
+                                            HttpException error) {
+                                        onFailedException.add(error);
+                                    }
+                                })
+                        .build();
+        request.start();
+
+        HttpException e = onFailedException.poll(5, TimeUnit.SECONDS);
+        assertNotNull(e);
+        assertTrue(e.getCause() instanceof IllegalArgumentException);
+        assertTrue(e.getCause().getMessage().contains("direct"));
+    }
+
+    @Test
+    public void testUrlRequest_fullByteBuffer() throws Exception {
+        BlockingQueue<HttpException> onFailedException = new ArrayBlockingQueue<>(1);
+
+        UrlRequest request =
+                mHttpEngine
+                        .newUrlRequestBuilder(
+                                mTestServer.getSuccessUrl(),
+                                Executors.newSingleThreadExecutor(),
+                                new StubUrlRequestCallback() {
+                                    @Override
+                                    public void onResponseStarted(
+                                            UrlRequest request, UrlResponseInfo info) {
+                                        ByteBuffer bb = ByteBuffer.allocateDirect(1024);
+                                        bb.position(bb.limit());
+                                        request.read(bb);
+                                    }
+
+                                    @Override
+                                    public void onFailed(
+                                            UrlRequest request,
+                                            UrlResponseInfo info,
+                                            HttpException error) {
+                                        onFailedException.add(error);
+                                    }
+                                })
+                        .build();
+        request.start();
+
+        HttpException e = onFailedException.poll(5, TimeUnit.SECONDS);
+        assertNotNull(e);
+        assertTrue(e.getCause() instanceof IllegalArgumentException);
+        assertTrue(e.getCause().getMessage().contains("full"));
+    }
+
+    @Test
+    public void testUrlRequest_redirects() throws Exception {
+        int expectedNumRedirects = 5;
+        String url =
+                mTestServer.getRedirectingAssetUrl("html/hello_world.html", expectedNumRedirects);
+
+        UrlRequest request = createUrlRequestBuilder(url).build();
+        request.start();
+
+        mCallback.expectCallback(ResponseStep.ON_SUCCEEDED);
+        UrlResponseInfo info = mCallback.mResponseInfo;
+        assertOKStatusCode(info);
+        assertThat(mCallback.mResponseAsString).contains("hello world");
+        assertThat(info.getUrlChain()).hasSize(expectedNumRedirects + 1);
+        assertThat(info.getUrlChain().get(0)).isEqualTo(url);
+        assertThat(info.getUrlChain().get(expectedNumRedirects)).isEqualTo(info.getUrl());
+    }
+
+    @Test
+    public void testUrlRequestPost_withRedirect() throws Exception {
+        String body = Strings.repeat(
+                "Hello, this is a really interesting body, so write this 100 times.", 100);
+
+        String redirectUrlParameter =
+                URLEncoder.encode(mTestServer.getEchoBodyUrl(), "UTF-8");
+        createUrlRequestBuilder(
+                String.format(
+                        "%s/alt_redirect?dest=%s&statusCode=307",
+                        mTestServer.getBaseUri(),
+                        redirectUrlParameter))
+                .setHttpMethod("POST")
+                .addHeader("Content-Type", "text/plain")
+                .setUploadDataProvider(
+                        UploadDataProviders.create(body.getBytes(StandardCharsets.UTF_8)),
+                        mCallback.getExecutor())
+                .build()
+                .start();
+        mCallback.expectCallback(ResponseStep.ON_SUCCEEDED);
+
+        assertOKStatusCode(mCallback.mResponseInfo);
+        assertThat(mCallback.mResponseAsString).isEqualTo(body);
+    }
+
+    private static class StubUrlRequestCallback implements UrlRequest.Callback {
+
+        @Override
+        public void onRedirectReceived(
+                UrlRequest request, UrlResponseInfo info, String newLocationUrl) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public void onResponseStarted(UrlRequest request, UrlResponseInfo info) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public void onReadCompleted(
+                UrlRequest request, UrlResponseInfo info, ByteBuffer byteBuffer) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public void onSucceeded(UrlRequest request, UrlResponseInfo info) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public void onFailed(UrlRequest request, UrlResponseInfo info, HttpException error) {
+            throw new UnsupportedOperationException(error);
+        }
+
+        @Override
+        public void onCanceled(@NonNull UrlRequest request, @Nullable UrlResponseInfo info) {
+            throw new UnsupportedOperationException();
+        }
+    }
+
+    private static class InMemoryUploadDataProvider extends UploadDataProvider {
+        private final byte[] mBody;
+        private int mNextChunkStartIndex = 0;
+
+        private InMemoryUploadDataProvider(byte[] body) {
+            this.mBody = body;
+        }
+
+        static InMemoryUploadDataProvider fromUtf8String(String body) {
+            return new InMemoryUploadDataProvider(body.getBytes(StandardCharsets.UTF_8));
+        }
+
+        @Override
+        public long getLength() {
+            return mBody.length;
+        }
+
+        @Override
+        public void read(UploadDataSink uploadDataSink, ByteBuffer byteBuffer) {
+            if (mNextChunkStartIndex >= getLength()) {
+                throw new IllegalStateException("Body of known length is exhausted");
+            }
+            int nextChunkSize =
+                    Math.min(mBody.length - mNextChunkStartIndex, byteBuffer.remaining());
+            byteBuffer.put(mBody, mNextChunkStartIndex, nextChunkSize);
+            mNextChunkStartIndex += nextChunkSize;
+            uploadDataSink.onReadSucceeded(false);
+        }
+
+        @Override
+        public void rewind(UploadDataSink uploadDataSink) {
+            mNextChunkStartIndex = 0;
+        }
+    }
 }
diff --git a/Cronet/tests/cts/src/android/net/http/cts/UrlResponseInfoTest.kt b/Cronet/tests/cts/src/android/net/http/cts/UrlResponseInfoTest.kt
new file mode 100644
index 0000000..38da9c5
--- /dev/null
+++ b/Cronet/tests/cts/src/android/net/http/cts/UrlResponseInfoTest.kt
@@ -0,0 +1,67 @@
+/*
+ * 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.content.Context
+import android.net.http.HttpEngine
+import android.net.http.cts.util.HttpCtsTestServer
+import android.net.http.cts.util.TestUrlRequestCallback
+import android.net.http.cts.util.TestUrlRequestCallback.ResponseStep
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertFalse
+import kotlin.test.assertTrue
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class UrlResponseInfoTest {
+
+    @Test
+    fun testUrlResponseInfo_apisReturnCorrectInfo() {
+        // start the engine and send a request
+        val context: Context = ApplicationProvider.getApplicationContext()
+        val server = HttpCtsTestServer(context)
+        val httpEngine = HttpEngine.Builder(context).build()
+        val callback = TestUrlRequestCallback()
+        val url = server.successUrl
+        val request = httpEngine.newUrlRequestBuilder(url, callback.executor, callback).build()
+
+        request.start()
+        callback.expectCallback(ResponseStep.ON_SUCCEEDED)
+
+        val info = callback.mResponseInfo
+        assertFalse(info.headers.asList.isEmpty())
+        assertEquals(200, info.httpStatusCode)
+        assertTrue(info.receivedByteCount > 0)
+        assertEquals(url, info.url)
+        assertEquals(listOf(url), info.urlChain)
+        assertFalse(info.wasCached())
+
+        // TODO Current test server does not set these values. Uncomment when we use one that does.
+        // assertEquals("OK", info.httpStatusText)
+        // assertEquals("http/1.1", info.negotiatedProtocol)
+
+        // cronet defaults to port 0 when no proxy is specified.
+        // This is not a behaviour we want to enforce since null is reasonable too.
+        // assertEquals(":0", info.proxyServer)
+
+        server.shutdown()
+        httpEngine.shutdown()
+    }
+}
diff --git a/Cronet/tests/cts/src/android/net/http/cts/util/TestBidirectionalStreamCallback.java b/Cronet/tests/cts/src/android/net/http/cts/util/TestBidirectionalStreamCallback.java
new file mode 100644
index 0000000..1e7333c
--- /dev/null
+++ b/Cronet/tests/cts/src/android/net/http/cts/util/TestBidirectionalStreamCallback.java
@@ -0,0 +1,485 @@
+/*
+ * 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.util;
+
+import static org.hamcrest.Matchers.equalTo;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeThat;
+import static org.junit.Assume.assumeTrue;
+
+import android.net.http.BidirectionalStream;
+import android.net.http.HeaderBlock;
+import android.net.http.HttpException;
+import android.net.http.UrlResponseInfo;
+import android.os.ConditionVariable;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ThreadFactory;
+
+/**
+ * Callback that tracks information from different callbacks and has a method to block thread until
+ * the stream completes on another thread. Allows to cancel, block stream or throw an exception from
+ * an arbitrary step.
+ */
+public class TestBidirectionalStreamCallback implements BidirectionalStream.Callback {
+    private static final int TIMEOUT_MS = 12_000;
+    public UrlResponseInfo mResponseInfo;
+    public HttpException mError;
+
+    public ResponseStep mResponseStep = ResponseStep.NOTHING;
+
+    public boolean mOnErrorCalled;
+    public boolean mOnCanceledCalled;
+
+    public int mHttpResponseDataLength;
+    public String mResponseAsString = "";
+
+    public HeaderBlock mTrailers;
+
+    private static final int READ_BUFFER_SIZE = 32 * 1024;
+
+    // When false, the consumer is responsible for all calls into the stream
+    // that advance it.
+    private boolean mAutoAdvance = true;
+
+    // Conditionally fail on certain steps.
+    private FailureType mFailureType = FailureType.NONE;
+    private ResponseStep mFailureStep = ResponseStep.NOTHING;
+
+    // Signals when the stream is done either successfully or not.
+    private final ConditionVariable mDone = new ConditionVariable();
+
+    // Signaled on each step when mAutoAdvance is false.
+    private final ConditionVariable mReadStepBlock = new ConditionVariable();
+    private final ConditionVariable mWriteStepBlock = new ConditionVariable();
+
+    // Executor Service for Cronet callbacks.
+    private final ExecutorService mExecutorService =
+            Executors.newSingleThreadExecutor(new ExecutorThreadFactory());
+    private Thread mExecutorThread;
+
+    // position() of ByteBuffer prior to read() call.
+    private int mBufferPositionBeforeRead;
+
+    // Data to write.
+    private final ArrayList<WriteBuffer> mWriteBuffers = new ArrayList<WriteBuffer>();
+
+    // Buffers that we yet to receive the corresponding onWriteCompleted callback.
+    private final ArrayList<WriteBuffer> mWriteBuffersToBeAcked = new ArrayList<WriteBuffer>();
+
+    // Whether to use a direct executor.
+    private final boolean mUseDirectExecutor;
+    private final DirectExecutor mDirectExecutor;
+
+    private class ExecutorThreadFactory implements ThreadFactory {
+        @Override
+        public Thread newThread(Runnable r) {
+            mExecutorThread = new Thread(r);
+            return mExecutorThread;
+        }
+    }
+
+    private static class WriteBuffer {
+        final ByteBuffer mBuffer;
+        final boolean mFlush;
+
+        WriteBuffer(ByteBuffer buffer, boolean flush) {
+            mBuffer = buffer;
+            mFlush = flush;
+        }
+    }
+
+    private static class DirectExecutor implements Executor {
+        @Override
+        public void execute(Runnable task) {
+            task.run();
+        }
+    }
+
+    public enum ResponseStep {
+        NOTHING,
+        ON_STREAM_READY,
+        ON_RESPONSE_STARTED,
+        ON_READ_COMPLETED,
+        ON_WRITE_COMPLETED,
+        ON_TRAILERS,
+        ON_CANCELED,
+        ON_FAILED,
+        ON_SUCCEEDED,
+    }
+
+    public enum FailureType {
+        NONE,
+        CANCEL_SYNC,
+        CANCEL_ASYNC,
+        // Same as above, but continues to advance the stream after posting
+        // the cancellation task.
+        CANCEL_ASYNC_WITHOUT_PAUSE,
+        THROW_SYNC
+    }
+
+    private boolean isTerminalCallback(ResponseStep step) {
+        switch (step) {
+            case ON_SUCCEEDED:
+            case ON_CANCELED:
+            case ON_FAILED:
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    public TestBidirectionalStreamCallback() {
+        mUseDirectExecutor = false;
+        mDirectExecutor = null;
+    }
+
+    public TestBidirectionalStreamCallback(boolean useDirectExecutor) {
+        mUseDirectExecutor = useDirectExecutor;
+        mDirectExecutor = new DirectExecutor();
+    }
+
+    public void setAutoAdvance(boolean autoAdvance) {
+        mAutoAdvance = autoAdvance;
+    }
+
+    public void setFailure(FailureType failureType, ResponseStep failureStep) {
+        mFailureStep = failureStep;
+        mFailureType = failureType;
+    }
+
+    public boolean blockForDone() {
+        return mDone.block(TIMEOUT_MS);
+    }
+
+    /**
+     * Waits for a terminal callback to complete execution before failing if the callback is not the
+     * expected one
+     *
+     * @param expectedStep the expected callback step
+     */
+    public void expectCallback(ResponseStep expectedStep) {
+        if (isTerminalCallback(expectedStep)) {
+            assertTrue(String.format(
+                            "Request timed out. Expected %s callback. Current callback is %s",
+                            expectedStep, mResponseStep),
+                    blockForDone());
+        }
+        assertSame(expectedStep, mResponseStep);
+    }
+
+    /**
+     * Waits for a terminal callback to complete execution before skipping the test if the callback
+     * is not the expected one
+     *
+     * @param expectedStep the expected callback step
+     */
+    public void assumeCallback(ResponseStep expectedStep) {
+        if (isTerminalCallback(expectedStep)) {
+            assumeTrue(
+                    String.format(
+                            "Request timed out. Expected %s callback. Current callback is %s",
+                            expectedStep, mResponseStep),
+                    blockForDone());
+        }
+        assumeThat(expectedStep, equalTo(mResponseStep));
+    }
+
+    public void waitForNextReadStep() {
+        mReadStepBlock.block();
+        mReadStepBlock.close();
+    }
+
+    public void waitForNextWriteStep() {
+        mWriteStepBlock.block();
+        mWriteStepBlock.close();
+    }
+
+    public Executor getExecutor() {
+        if (mUseDirectExecutor) {
+            return mDirectExecutor;
+        }
+        return mExecutorService;
+    }
+
+    public void shutdownExecutor() {
+        if (mUseDirectExecutor) {
+            throw new UnsupportedOperationException("DirectExecutor doesn't support shutdown");
+        }
+        mExecutorService.shutdown();
+    }
+
+    public void addWriteData(byte[] data) {
+        addWriteData(data, true);
+    }
+
+    public void addWriteData(byte[] data, boolean flush) {
+        ByteBuffer writeBuffer = ByteBuffer.allocateDirect(data.length);
+        writeBuffer.put(data);
+        writeBuffer.flip();
+        mWriteBuffers.add(new WriteBuffer(writeBuffer, flush));
+        mWriteBuffersToBeAcked.add(new WriteBuffer(writeBuffer, flush));
+    }
+
+    @Override
+    public void onStreamReady(BidirectionalStream stream) {
+        checkOnValidThread();
+        assertFalse(stream.isDone());
+        assertEquals(ResponseStep.NOTHING, mResponseStep);
+        assertNull(mError);
+        mResponseStep = ResponseStep.ON_STREAM_READY;
+        if (maybeThrowCancelOrPause(stream, mWriteStepBlock)) {
+            return;
+        }
+        startNextWrite(stream);
+    }
+
+    @Override
+    public void onResponseHeadersReceived(BidirectionalStream stream, UrlResponseInfo info) {
+        checkOnValidThread();
+        assertFalse(stream.isDone());
+        assertTrue(
+                mResponseStep == ResponseStep.NOTHING
+                        || mResponseStep == ResponseStep.ON_STREAM_READY
+                        || mResponseStep == ResponseStep.ON_WRITE_COMPLETED);
+        assertNull(mError);
+
+        mResponseStep = ResponseStep.ON_RESPONSE_STARTED;
+        mResponseInfo = info;
+        if (maybeThrowCancelOrPause(stream, mReadStepBlock)) {
+            return;
+        }
+        startNextRead(stream);
+    }
+
+    @Override
+    public void onReadCompleted(
+            BidirectionalStream stream,
+            UrlResponseInfo info,
+            ByteBuffer byteBuffer,
+            boolean endOfStream) {
+        checkOnValidThread();
+        assertFalse(stream.isDone());
+        assertTrue(
+                mResponseStep == ResponseStep.ON_RESPONSE_STARTED
+                        || mResponseStep == ResponseStep.ON_READ_COMPLETED
+                        || mResponseStep == ResponseStep.ON_WRITE_COMPLETED
+                        || mResponseStep == ResponseStep.ON_TRAILERS);
+        assertNull(mError);
+
+        mResponseStep = ResponseStep.ON_READ_COMPLETED;
+        mResponseInfo = info;
+
+        final int bytesRead = byteBuffer.position() - mBufferPositionBeforeRead;
+        mHttpResponseDataLength += bytesRead;
+        final byte[] lastDataReceivedAsBytes = new byte[bytesRead];
+        // Rewind byteBuffer.position() to pre-read() position.
+        byteBuffer.position(mBufferPositionBeforeRead);
+        // This restores byteBuffer.position() to its value on entrance to
+        // this function.
+        byteBuffer.get(lastDataReceivedAsBytes);
+
+        mResponseAsString += new String(lastDataReceivedAsBytes);
+
+        if (maybeThrowCancelOrPause(stream, mReadStepBlock)) {
+            return;
+        }
+        // Do not read if EOF has been reached.
+        if (!endOfStream) {
+            startNextRead(stream);
+        }
+    }
+
+    @Override
+    public void onWriteCompleted(
+            BidirectionalStream stream,
+            UrlResponseInfo info,
+            ByteBuffer buffer,
+            boolean endOfStream) {
+        checkOnValidThread();
+        assertFalse(stream.isDone());
+        assertNull(mError);
+        mResponseStep = ResponseStep.ON_WRITE_COMPLETED;
+        mResponseInfo = info;
+        if (!mWriteBuffersToBeAcked.isEmpty()) {
+            assertEquals(buffer, mWriteBuffersToBeAcked.get(0).mBuffer);
+            mWriteBuffersToBeAcked.remove(0);
+        }
+        if (maybeThrowCancelOrPause(stream, mWriteStepBlock)) {
+            return;
+        }
+        startNextWrite(stream);
+    }
+
+    @Override
+    public void onResponseTrailersReceived(
+            BidirectionalStream stream,
+            UrlResponseInfo info,
+            HeaderBlock trailers) {
+        checkOnValidThread();
+        assertFalse(stream.isDone());
+        assertNull(mError);
+        mResponseStep = ResponseStep.ON_TRAILERS;
+        mResponseInfo = info;
+        mTrailers = trailers;
+        if (maybeThrowCancelOrPause(stream, mReadStepBlock)) {
+            return;
+        }
+    }
+
+    @Override
+    public void onSucceeded(BidirectionalStream stream, UrlResponseInfo info) {
+        checkOnValidThread();
+        assertTrue(stream.isDone());
+        assertTrue(
+                mResponseStep == ResponseStep.ON_RESPONSE_STARTED
+                        || mResponseStep == ResponseStep.ON_READ_COMPLETED
+                        || mResponseStep == ResponseStep.ON_WRITE_COMPLETED
+                        || mResponseStep == ResponseStep.ON_TRAILERS);
+        assertFalse(mOnErrorCalled);
+        assertFalse(mOnCanceledCalled);
+        assertNull(mError);
+        assertEquals(0, mWriteBuffers.size());
+        assertEquals(0, mWriteBuffersToBeAcked.size());
+
+        mResponseStep = ResponseStep.ON_SUCCEEDED;
+        mResponseInfo = info;
+        openDone();
+        maybeThrowCancelOrPause(stream, mReadStepBlock);
+    }
+
+    @Override
+    public void onFailed(BidirectionalStream stream, UrlResponseInfo info, HttpException error) {
+        checkOnValidThread();
+        assertTrue(stream.isDone());
+        // Shouldn't happen after success.
+        assertTrue(mResponseStep != ResponseStep.ON_SUCCEEDED);
+        // Should happen at most once for a single stream.
+        assertFalse(mOnErrorCalled);
+        assertFalse(mOnCanceledCalled);
+        assertNull(mError);
+        mResponseStep = ResponseStep.ON_FAILED;
+        mResponseInfo = info;
+
+        mOnErrorCalled = true;
+        mError = error;
+        openDone();
+        maybeThrowCancelOrPause(stream, mReadStepBlock);
+    }
+
+    @Override
+    public void onCanceled(BidirectionalStream stream, UrlResponseInfo info) {
+        checkOnValidThread();
+        assertTrue(stream.isDone());
+        // Should happen at most once for a single stream.
+        assertFalse(mOnCanceledCalled);
+        assertFalse(mOnErrorCalled);
+        assertNull(mError);
+        mResponseStep = ResponseStep.ON_CANCELED;
+        mResponseInfo = info;
+
+        mOnCanceledCalled = true;
+        openDone();
+        maybeThrowCancelOrPause(stream, mReadStepBlock);
+    }
+
+    public void startNextRead(BidirectionalStream stream) {
+        startNextRead(stream, ByteBuffer.allocateDirect(READ_BUFFER_SIZE));
+    }
+
+    public void startNextRead(BidirectionalStream stream, ByteBuffer buffer) {
+        mBufferPositionBeforeRead = buffer.position();
+        stream.read(buffer);
+    }
+
+    public void startNextWrite(BidirectionalStream stream) {
+        if (!mWriteBuffers.isEmpty()) {
+            Iterator<WriteBuffer> iterator = mWriteBuffers.iterator();
+            while (iterator.hasNext()) {
+                WriteBuffer b = iterator.next();
+                stream.write(b.mBuffer, !iterator.hasNext());
+                iterator.remove();
+                if (b.mFlush) {
+                    stream.flush();
+                    break;
+                }
+            }
+        }
+    }
+
+    public boolean isDone() {
+        // It's not mentioned by the Android docs, but block(0) seems to block
+        // indefinitely, so have to block for one millisecond to get state
+        // without blocking.
+        return mDone.block(1);
+    }
+
+    /** Returns the number of pending Writes. */
+    public int numPendingWrites() {
+        return mWriteBuffers.size();
+    }
+
+    protected void openDone() {
+        mDone.open();
+    }
+
+    /** Returns {@code false} if the callback should continue to advance the stream. */
+    private boolean maybeThrowCancelOrPause(
+            final BidirectionalStream stream, ConditionVariable stepBlock) {
+        if (mResponseStep != mFailureStep || mFailureType == FailureType.NONE) {
+            if (!mAutoAdvance) {
+                stepBlock.open();
+                return true;
+            }
+            return false;
+        }
+
+        if (mFailureType == FailureType.THROW_SYNC) {
+            throw new IllegalStateException("Callback Exception.");
+        }
+        Runnable task =
+                new Runnable() {
+                    @Override
+                    public void run() {
+                        stream.cancel();
+                    }
+                };
+        if (mFailureType == FailureType.CANCEL_ASYNC
+                || mFailureType == FailureType.CANCEL_ASYNC_WITHOUT_PAUSE) {
+            getExecutor().execute(task);
+        } else {
+            task.run();
+        }
+        return mFailureType != FailureType.CANCEL_ASYNC_WITHOUT_PAUSE;
+    }
+
+    /** Checks whether callback methods are invoked on the correct thread. */
+    private void checkOnValidThread() {
+        if (!mUseDirectExecutor) {
+            assertEquals(mExecutorThread, Thread.currentThread());
+        }
+    }
+}
diff --git a/Cronet/tests/cts/src/android/net/http/cts/util/TestStatusListener.kt b/Cronet/tests/cts/src/android/net/http/cts/util/TestStatusListener.kt
index e526c7d..3a4486f 100644
--- a/Cronet/tests/cts/src/android/net/http/cts/util/TestStatusListener.kt
+++ b/Cronet/tests/cts/src/android/net/http/cts/util/TestStatusListener.kt
@@ -24,7 +24,7 @@
 private const val TIMEOUT_MS = 12000L
 
 /** Test status listener for requests */
-class TestStatusListener : StatusListener() {
+class TestStatusListener : StatusListener {
     private val statusFuture = CompletableFuture<Int>()
 
     override fun onStatus(status: Int) {
diff --git a/Cronet/tests/cts/src/android/net/http/cts/util/TestUrlRequestCallback.java b/Cronet/tests/cts/src/android/net/http/cts/util/TestUrlRequestCallback.java
index efbcff6..28443b7 100644
--- a/Cronet/tests/cts/src/android/net/http/cts/util/TestUrlRequestCallback.java
+++ b/Cronet/tests/cts/src/android/net/http/cts/util/TestUrlRequestCallback.java
@@ -50,7 +50,7 @@
  * method to block thread until the request completes on another thread.
  * Allows us to cancel, block request or throw an exception from an arbitrary step.
  */
-public class TestUrlRequestCallback extends UrlRequest.Callback {
+public class TestUrlRequestCallback implements UrlRequest.Callback {
     private static final int TIMEOUT_MS = 12_000;
     public ArrayList<UrlResponseInfo> mRedirectResponseInfoList = new ArrayList<>();
     public ArrayList<String> mRedirectUrlList = new ArrayList<>();
diff --git a/Cronet/tests/cts/src/android/net/http/cts/util/TestUtils.kt b/Cronet/tests/cts/src/android/net/http/cts/util/TestUtils.kt
index 23ec2c8..7fc005a 100644
--- a/Cronet/tests/cts/src/android/net/http/cts/util/TestUtils.kt
+++ b/Cronet/tests/cts/src/android/net/http/cts/util/TestUtils.kt
@@ -27,7 +27,8 @@
 fun skipIfNoInternetConnection(context: Context) {
     val connectivityManager = context.getSystemService(ConnectivityManager::class.java)
     assumeNotNull(
-        "This test requires a working Internet connection", connectivityManager.getActiveNetwork())
+        "This test requires a working Internet connection", connectivityManager!!.activeNetwork
+    )
 }
 
 fun assertOKStatusCode(info: UrlResponseInfo) {
@@ -35,5 +36,5 @@
 }
 
 fun assumeOKStatusCode(info: UrlResponseInfo) {
-    assumeThat("Status code must be 200 OK", info.getHttpStatusCode(), equalTo(200))
+    assumeThat("Status code must be 200 OK", info.httpStatusCode, equalTo(200))
 }
diff --git a/Cronet/tests/cts/src/android/net/http/cts/util/UploadDataProviders.java b/Cronet/tests/cts/src/android/net/http/cts/util/UploadDataProviders.java
new file mode 100644
index 0000000..889f8f2
--- /dev/null
+++ b/Cronet/tests/cts/src/android/net/http/cts/util/UploadDataProviders.java
@@ -0,0 +1,198 @@
+/*
+ * 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.util;
+
+import android.net.http.UploadDataProvider;
+import android.net.http.UploadDataSink;
+import android.os.ParcelFileDescriptor;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+
+/**
+ * Provides implementations of {@link UploadDataProvider} for common use cases. Corresponds to
+ * {@code android.net.http.apihelpers.UploadDataProviders} which is not an exposed API.
+ */
+public final class UploadDataProviders {
+    /**
+     * Uploads an entire file.
+     *
+     * @param file The file to upload
+     * @return A new UploadDataProvider for the given file
+     */
+    public static UploadDataProvider create(final File file) {
+        return new FileUploadProvider(() -> new FileInputStream(file).getChannel());
+    }
+
+    /**
+     * Uploads an entire file, closing the descriptor when it is no longer needed.
+     *
+     * @param fd The file descriptor to upload
+     * @throws IllegalArgumentException if {@code fd} is not a file.
+     * @return A new UploadDataProvider for the given file descriptor
+     */
+    public static UploadDataProvider create(final ParcelFileDescriptor fd) {
+        return new FileUploadProvider(() -> {
+            if (fd.getStatSize() != -1) {
+                return new ParcelFileDescriptor.AutoCloseInputStream(fd).getChannel();
+            } else {
+                fd.close();
+                throw new IllegalArgumentException("Not a file: " + fd);
+            }
+        });
+    }
+
+    /**
+     * Uploads a ByteBuffer, from the current {@code buffer.position()} to {@code buffer.limit()}
+     *
+     * @param buffer The data to upload
+     * @return A new UploadDataProvider for the given buffer
+     */
+    public static UploadDataProvider create(ByteBuffer buffer) {
+        return new ByteBufferUploadProvider(buffer.slice());
+    }
+
+    /**
+     * Uploads {@code length} bytes from {@code data}, starting from {@code offset}
+     *
+     * @param data Array containing data to upload
+     * @param offset Offset within data to start with
+     * @param length Number of bytes to upload
+     * @return A new UploadDataProvider for the given data
+     */
+    public static UploadDataProvider create(byte[] data, int offset, int length) {
+        return new ByteBufferUploadProvider(ByteBuffer.wrap(data, offset, length).slice());
+    }
+
+    /**
+     * Uploads the contents of {@code data}
+     *
+     * @param data Array containing data to upload
+     * @return A new UploadDataProvider for the given data
+     */
+    public static UploadDataProvider create(byte[] data) {
+        return create(data, 0, data.length);
+    }
+
+    private interface FileChannelProvider {
+        FileChannel getChannel() throws IOException;
+    }
+
+    private static final class FileUploadProvider extends UploadDataProvider {
+        private volatile FileChannel mChannel;
+        private final FileChannelProvider mProvider;
+        /** Guards initialization of {@code mChannel} */
+        private final Object mLock = new Object();
+
+        private FileUploadProvider(FileChannelProvider provider) {
+            this.mProvider = provider;
+        }
+
+        @Override
+        public long getLength() throws IOException {
+            return getChannel().size();
+        }
+
+        @Override
+        public void read(UploadDataSink uploadDataSink, ByteBuffer byteBuffer) throws IOException {
+            if (!byteBuffer.hasRemaining()) {
+                throw new IllegalStateException("Cronet passed a buffer with no bytes remaining");
+            }
+            FileChannel channel = getChannel();
+            int bytesRead = 0;
+            while (bytesRead == 0) {
+                int read = channel.read(byteBuffer);
+                if (read == -1) {
+                    break;
+                } else {
+                    bytesRead += read;
+                }
+            }
+            uploadDataSink.onReadSucceeded(false);
+        }
+
+        @Override
+        public void rewind(UploadDataSink uploadDataSink) throws IOException {
+            getChannel().position(0);
+            uploadDataSink.onRewindSucceeded();
+        }
+
+        /**
+         * Lazily initializes the channel so that a blocking operation isn't performed
+         * on a non-executor thread.
+         */
+        private FileChannel getChannel() throws IOException {
+            if (mChannel == null) {
+                synchronized (mLock) {
+                    if (mChannel == null) {
+                        mChannel = mProvider.getChannel();
+                    }
+                }
+            }
+            return mChannel;
+        }
+
+        @Override
+        public void close() throws IOException {
+            FileChannel channel = mChannel;
+            if (channel != null) {
+                channel.close();
+            }
+        }
+    }
+
+    private static final class ByteBufferUploadProvider extends UploadDataProvider {
+        private final ByteBuffer mUploadBuffer;
+
+        private ByteBufferUploadProvider(ByteBuffer uploadBuffer) {
+            this.mUploadBuffer = uploadBuffer;
+        }
+
+        @Override
+        public long getLength() {
+            return mUploadBuffer.limit();
+        }
+
+        @Override
+        public void read(UploadDataSink uploadDataSink, ByteBuffer byteBuffer) {
+            if (!byteBuffer.hasRemaining()) {
+                throw new IllegalStateException("Cronet passed a buffer with no bytes remaining");
+            }
+            if (byteBuffer.remaining() >= mUploadBuffer.remaining()) {
+                byteBuffer.put(mUploadBuffer);
+            } else {
+                int oldLimit = mUploadBuffer.limit();
+                mUploadBuffer.limit(mUploadBuffer.position() + byteBuffer.remaining());
+                byteBuffer.put(mUploadBuffer);
+                mUploadBuffer.limit(oldLimit);
+            }
+            uploadDataSink.onReadSucceeded(false);
+        }
+
+        @Override
+        public void rewind(UploadDataSink uploadDataSink) {
+            mUploadBuffer.position(0);
+            uploadDataSink.onRewindSucceeded();
+        }
+    }
+
+    // Prevent instantiation
+    private UploadDataProviders() {}
+}
diff --git a/Cronet/tests/mts/Android.bp b/Cronet/tests/mts/Android.bp
index 1cabd63..03d163c 100644
--- a/Cronet/tests/mts/Android.bp
+++ b/Cronet/tests/mts/Android.bp
@@ -20,6 +20,8 @@
 android_library {
     name: "NetHttpTestsLibPreJarJar",
     srcs: [":cronet_aml_javatests_sources"],
+    sdk_version: "test_current",
+    min_sdk_version: "30",
     static_libs: [
         "androidx.test.ext.junit",
         "androidx.test.rules",
@@ -28,7 +30,8 @@
     libs: [
         "android.test.base",
         "framework-tethering-pre-jarjar",
-    ]
+    ],
+    lint: { test: true }
 }
 
 android_test {
diff --git a/Cronet/tests/mts/AndroidTest.xml b/Cronet/tests/mts/AndroidTest.xml
index 8cb549e..0d780a1 100644
--- a/Cronet/tests/mts/AndroidTest.xml
+++ b/Cronet/tests/mts/AndroidTest.xml
@@ -16,8 +16,9 @@
   -->
 <configuration description="Runs NetHttp Mainline Tests.">
     <!-- Only run tests if the device under test is SDK version 30 or above. -->
+    <!-- TODO Switch back to Sdk30 when b/270049141 is fixed -->
     <object type="module_controller"
-            class="com.android.tradefed.testtype.suite.module.Sdk30ModuleController" />
+            class="com.android.tradefed.testtype.suite.module.Sdk31ModuleController" />
 
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="test-file-name" value="NetHttpTests.apk" />
diff --git a/Cronet/tools/import/copy.bara.sky b/Cronet/tools/import/copy.bara.sky
index 64256a0..8353fd3 100644
--- a/Cronet/tools/import/copy.bara.sky
+++ b/Cronet/tools/import/copy.bara.sky
@@ -17,8 +17,9 @@
     "**/Android.bp",
     "**/Android.mk",
 
-    # Exclude existing OWNERS files
-    "**/OWNERS",
+    # Exclude existing *OWNERS files
+    "**/*OWNERS",
+    "**/.git/**",
 ]
 
 cronet_origin_files = glob(
@@ -35,6 +36,8 @@
         "crypto/**",
         "ipc/**",
         "net/**",
+        # Note: Only used for tests.
+        "testing/**",
         "url/**",
         "LICENSE",
     ],
@@ -48,6 +51,8 @@
         "components/cronet/ios/**",
         "components/cronet/native/**",
 
+        # Per aosp/2399270
+        "testing/buildbot/**",
 
         # Exclude all third-party directories. Those are specified explicitly
         # below, so no dependency can accidentally creep in.
@@ -65,14 +70,11 @@
         "base/third_party/icu/**",
         "base/third_party/nspr/**",
         "base/third_party/superfasthash/**",
-        # TODO: we should be able to remove this dependency.
-        "base/third_party/symbolize/**",
         "base/third_party/valgrind/**",
-        "base/third_party/xdg_user_dirs/**",
-        # Not present in source repo; requires gclient sync.
         "buildtools/third_party/libc++/**",
-        # Not present in source repo; requires gclient sync.
         "buildtools/third_party/libc++abi/**",
+        # Note: Only used for tests.
+        "net/third_party/nist-pkits/**",
         "net/third_party/quiche/**",
         "net/third_party/uri_template/**",
         "third_party/abseil-cpp/**",
@@ -80,12 +82,21 @@
         "third_party/ashmem/**",
         "third_party/boringssl/**",
         "third_party/brotli/**",
-        # Not present in source repo; requires gclient sync.
+        # Note: Only used for tests.
+        "third_party/ced/**",
+        # Note: Only used for tests.
+        "third_party/googletest/**",
         "third_party/icu/**",
         "third_party/libevent/**",
+        # Note: Only used for tests.
+        "third_party/libxml/**",
+        # Note: Only used for tests.
+        "third_party/lss/**",
         "third_party/metrics_proto/**",
         "third_party/modp_b64/**",
         "third_party/protobuf/**",
+        # Note: Only used for tests.
+        "third_party/quic_trace/**",
         "third_party/zlib/**",
     ],
     exclude = common_excludes,
@@ -94,12 +105,8 @@
 core.workflow(
     name = "import_cronet",
     authoring = authoring.overwrite("Cronet Mainline Eng <cronet-mainline-eng+copybara@google.com>"),
-    origin = git.origin(
-        url = "rpc://chromium/chromium/src",
-        # Source ref is set by the invoking script.
-        ref = "overwritten-by-script",
-        partial_fetch = True,
-    ),
+    # Origin folder is specified via source_ref argument, see import_cronet.sh
+    origin = folder.origin(),
     origin_files = cronet_origin_files,
     destination = git.destination(
         # The destination URL is set by the invoking script.
diff --git a/Cronet/tools/import/import_cronet.sh b/Cronet/tools/import/import_cronet.sh
index 7642914..d0c8deb 100755
--- a/Cronet/tools/import/import_cronet.sh
+++ b/Cronet/tools/import/import_cronet.sh
@@ -19,40 +19,109 @@
 #  Environment:
 #   ANDROID_BUILD_TOP: path the root of the current Android directory.
 #  Arguments:
-#   -l: The last revision that was imported.
-#   -n: The new revision to import.
+#   -l rev: The last revision that was imported.
+#  Optional Arguments:
+#   -n rev: The new revision to import.
+#   -f: Force copybara to ignore a failure to find the last imported revision.
 
-OPTSTRING=l:n:
+OPTSTRING=fl:n:
 
 usage() {
     cat <<EOF
-Usage: import_cronet.sh -l last-rev -n new-rev
+Usage: import_cronet.sh -n new-rev [-l last-rev] [-f]
 EOF
     exit 1
 }
 
+COPYBARA_FOLDER_ORIGIN="/tmp/copybara-origin"
+
+#######################################
+# Create upstream-import branch in external/cronet.
+# Globals:
+#   ANDROID_BUILD_TOP
+# Arguments:
+#   none
+#######################################
+setup_upstream_import_branch() {
+    local git_dir="${ANDROID_BUILD_TOP}/external/cronet"
+    local initial_empty_repo_sha="d1add53d6e90815f363c91d433735556ce79b0d2"
+
+    # Suppress error message if branch already exists.
+    (cd "${git_dir}" && git branch upstream-import "${initial_empty_repo_sha}") 2>/dev/null
+}
+
+#######################################
+# Setup folder.origin for copybara inside /tmp
+# Globals:
+#   COPYBARA_FOLDER_ORIGIN
+# Arguments:
+#   new_rev, string
+#######################################
+setup_folder_origin() {
+    local _new_rev=$1
+    mkdir -p "${COPYBARA_FOLDER_ORIGIN}"
+    cd "${COPYBARA_FOLDER_ORIGIN}"
+
+    # For this to work _new_rev must be a branch or a tag.
+    git clone --depth=1 --branch "${_new_rev}" https://chromium.googlesource.com/chromium/src.git
+
+    cat <<EOF >.gclient
+solutions = [
+  {
+    "name": "src",
+    "url": "https://chromium.googlesource.com/chromium/src.git",
+    "managed": False,
+    "custom_deps": {},
+    "custom_vars": {},
+  },
+]
+target_os = ["android"]
+EOF
+    cd src
+    # Set appropriate gclient flags to speed up syncing.
+    gclient sync \
+        --no-history
+        --shallow
+}
+
 #######################################
 # Runs the copybara import of Chromium
 # Globals:
 #   ANDROID_BUILD_TOP
+#   COPYBARA_FOLDER_ORIGIN
 # Arguments:
-#   last_rev, string
-#   new_rev, string
+#   last_rev, string or empty
+#   force, string or empty
 #######################################
 do_run_copybara() {
     local _last_rev=$1
-    local _new_rev=$2
+    local _force=$2
+
+    local -a flags
+    flags+=(--git-destination-url="file://${ANDROID_BUILD_TOP}/external/cronet")
+    flags+=(--repo-timeout 3m)
+
+    # buildtools/third_party/libc++ contains an invalid symlink
+    flags+=(--folder-origin-ignore-invalid-symlinks)
+    flags+=(--git-no-verify)
+
+    if [ ! -z "${_force}" ]; then
+        flags+=(--force)
+    fi
+
+    if [ ! -z "${_last_rev}" ]; then
+        flags+=(--last-rev "${_last_rev}")
+    fi
 
     /google/bin/releases/copybara/public/copybara/copybara \
-        --git-destination-url="file://${ANDROID_BUILD_TOP}/external/cronet" \
-        --last-rev "${_last_rev}" \
-        --repo-timeout 3h \
+        "${flags[@]}" \
         "${ANDROID_BUILD_TOP}/packages/modules/Connectivity/Cronet/tools/import/copy.bara.sky" \
-        import_cronet "${_new_rev}"
+        import_cronet "${COPYBARA_FOLDER_ORIGIN}/src"
 }
 
 while getopts $OPTSTRING opt; do
     case "${opt}" in
+        f) force=true ;;
         l) last_rev="${OPTARG}" ;;
         n) new_rev="${OPTARG}" ;;
         ?) usage ;;
@@ -60,17 +129,12 @@
     esac
 done
 
-# TODO: Get last-rev from METADATA file.
-# Setting last-rev may only be required for the first commit.
-if [ -z "${last_rev}" ]; then
-    echo "-l argument required"
-    usage
-fi
-
 if [ -z "${new_rev}" ]; then
     echo "-n argument required"
     usage
 fi
 
-do_run_copybara "${last_rev}" "${new_rev}"
+setup_upstream_import_branch
+setup_folder_origin "${new_rev}"
+do_run_copybara "${last_rev}" "${force}"
 
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 34646e2..70c5f85 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -58,6 +58,17 @@
       ]
     },
     {
+      "name": "CtsNetTestCasesMaxTargetSdk33",
+      "options": [
+        {
+          "exclude-annotation": "com.android.testutils.SkipPresubmit"
+        },
+        {
+          "exclude-annotation": "androidx.test.filters.RequiresDevice"
+        }
+      ]
+    },
+    {
       "name": "bpf_existence_test"
     },
     {
@@ -143,6 +154,17 @@
         }
       ]
     },
+    {
+      "name": "CtsNetTestCasesMaxTargetSdk33[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]",
+      "options": [
+        {
+          "exclude-annotation": "com.android.testutils.SkipPresubmit"
+        },
+        {
+          "exclude-annotation": "androidx.test.filters.RequiresDevice"
+        }
+      ]
+    },
     // Test with APK modules only, in cases where APEX is not supported, or the other modules
     // were simply not updated
     {
@@ -191,6 +213,9 @@
     },
     {
       "name": "libnetworkstats_test[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]"
+    },
+    {
+      "name": "NetHttpCoverageTests[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]"
     }
   ],
   "mainline-postsubmit": [
diff --git a/Tethering/common/TetheringLib/Android.bp b/Tethering/common/TetheringLib/Android.bp
index 4c677d0..4080029 100644
--- a/Tethering/common/TetheringLib/Android.bp
+++ b/Tethering/common/TetheringLib/Android.bp
@@ -65,6 +65,7 @@
 
     hostdex: true, // for hiddenapi check
     permitted_packages: ["android.net"],
+    lint: { strict_updatability_linting: true },
 }
 
 java_defaults {
@@ -81,20 +82,11 @@
     impl_only_static_libs: [
         "cronet_aml_java",
     ],
-    // STOPSHIP(b/265674359): fix all Cronet lint warnings and re-enable lint
-    // directly in framework-tethering
-    lint: {
-         enabled: false,
-    },
-    api_lint: {
-        enabled: false,
-    },
     api_dir: "cronet_enabled/api",
 }
 
 java_defaults {
   name: "CronetJavaDefaultsDisabled",
-  lint: { strict_updatability_linting: true },
 }
 
 java_defaults {
diff --git a/Tethering/common/TetheringLib/cronet_enabled/api/current.txt b/Tethering/common/TetheringLib/cronet_enabled/api/current.txt
index 777138d..66a0295 100644
--- a/Tethering/common/TetheringLib/cronet_enabled/api/current.txt
+++ b/Tethering/common/TetheringLib/cronet_enabled/api/current.txt
@@ -5,21 +5,18 @@
     ctor public BidirectionalStream();
     method public abstract void cancel();
     method public abstract void flush();
+    method @NonNull public abstract android.net.http.HeaderBlock getHeaders();
+    method @NonNull public abstract String getHttpMethod();
+    method public abstract int getPriority();
+    method public abstract int getTrafficStatsTag();
+    method public abstract int getTrafficStatsUid();
+    method public abstract boolean hasTrafficStatsTag();
+    method public abstract boolean hasTrafficStatsUid();
+    method public abstract boolean isDelayRequestHeadersUntilFirstFlushEnabled();
     method public abstract boolean isDone();
-    method public abstract void read(java.nio.ByteBuffer);
+    method public abstract void read(@NonNull java.nio.ByteBuffer);
     method public abstract void start();
-    method public abstract void write(java.nio.ByteBuffer, boolean);
-  }
-
-  public abstract static class BidirectionalStream.Builder {
-    ctor public BidirectionalStream.Builder();
-    method public abstract android.net.http.BidirectionalStream.Builder addHeader(String, String);
-    method public abstract android.net.http.BidirectionalStream build();
-    method public abstract android.net.http.BidirectionalStream.Builder delayRequestHeadersUntilFirstFlush(boolean);
-    method public abstract android.net.http.BidirectionalStream.Builder setHttpMethod(String);
-    method public abstract android.net.http.BidirectionalStream.Builder setPriority(int);
-    method public abstract android.net.http.BidirectionalStream.Builder setTrafficStatsTag(int);
-    method public abstract android.net.http.BidirectionalStream.Builder setTrafficStatsUid(int);
+    method public abstract void write(@NonNull java.nio.ByteBuffer, boolean);
     field public static final int STREAM_PRIORITY_HIGHEST = 4; // 0x4
     field public static final int STREAM_PRIORITY_IDLE = 0; // 0x0
     field public static final int STREAM_PRIORITY_LOW = 2; // 0x2
@@ -27,98 +24,120 @@
     field public static final int STREAM_PRIORITY_MEDIUM = 3; // 0x3
   }
 
-  public abstract static class BidirectionalStream.Callback {
-    ctor public BidirectionalStream.Callback();
-    method public void onCanceled(android.net.http.BidirectionalStream, android.net.http.UrlResponseInfo);
-    method public abstract void onFailed(android.net.http.BidirectionalStream, android.net.http.UrlResponseInfo, android.net.http.HttpException);
-    method public abstract void onReadCompleted(android.net.http.BidirectionalStream, android.net.http.UrlResponseInfo, java.nio.ByteBuffer, boolean);
-    method public abstract void onResponseHeadersReceived(android.net.http.BidirectionalStream, android.net.http.UrlResponseInfo);
-    method public void onResponseTrailersReceived(android.net.http.BidirectionalStream, android.net.http.UrlResponseInfo, android.net.http.UrlResponseInfo.HeaderBlock);
-    method public abstract void onStreamReady(android.net.http.BidirectionalStream);
-    method public abstract void onSucceeded(android.net.http.BidirectionalStream, android.net.http.UrlResponseInfo);
-    method public abstract void onWriteCompleted(android.net.http.BidirectionalStream, android.net.http.UrlResponseInfo, java.nio.ByteBuffer, boolean);
+  public abstract static class BidirectionalStream.Builder {
+    ctor public BidirectionalStream.Builder();
+    method @NonNull public abstract android.net.http.BidirectionalStream.Builder addHeader(@NonNull String, @NonNull String);
+    method @NonNull public abstract android.net.http.BidirectionalStream build();
+    method @NonNull public abstract android.net.http.BidirectionalStream.Builder setDelayRequestHeadersUntilFirstFlushEnabled(boolean);
+    method @NonNull public abstract android.net.http.BidirectionalStream.Builder setHttpMethod(@NonNull String);
+    method @NonNull public abstract android.net.http.BidirectionalStream.Builder setPriority(int);
+    method @NonNull public abstract android.net.http.BidirectionalStream.Builder setTrafficStatsTag(int);
+    method @NonNull public abstract android.net.http.BidirectionalStream.Builder setTrafficStatsUid(int);
+  }
+
+  public static interface BidirectionalStream.Callback {
+    method public void onCanceled(@NonNull android.net.http.BidirectionalStream, @Nullable android.net.http.UrlResponseInfo);
+    method public void onFailed(@NonNull android.net.http.BidirectionalStream, @Nullable android.net.http.UrlResponseInfo, @NonNull android.net.http.HttpException);
+    method public void onReadCompleted(@NonNull android.net.http.BidirectionalStream, @NonNull android.net.http.UrlResponseInfo, @NonNull java.nio.ByteBuffer, boolean);
+    method public void onResponseHeadersReceived(@NonNull android.net.http.BidirectionalStream, @NonNull android.net.http.UrlResponseInfo);
+    method public void onResponseTrailersReceived(@NonNull android.net.http.BidirectionalStream, @NonNull android.net.http.UrlResponseInfo, @NonNull android.net.http.HeaderBlock);
+    method public void onStreamReady(@NonNull android.net.http.BidirectionalStream);
+    method public void onSucceeded(@NonNull android.net.http.BidirectionalStream, @NonNull android.net.http.UrlResponseInfo);
+    method public void onWriteCompleted(@NonNull android.net.http.BidirectionalStream, @NonNull android.net.http.UrlResponseInfo, @NonNull java.nio.ByteBuffer, boolean);
   }
 
   public abstract class CallbackException extends android.net.http.HttpException {
-    ctor protected CallbackException(String, Throwable);
+    ctor protected CallbackException(@Nullable String, @Nullable Throwable);
   }
 
   public class ConnectionMigrationOptions {
-    method @Nullable public Boolean getAllowNonDefaultNetworkUsage();
-    method @Nullable public Boolean getEnableDefaultNetworkMigration();
-    method @Nullable public Boolean getEnablePathDegradationMigration();
+    method public int getAllowNonDefaultNetworkUsageEnabled();
+    method public int getDefaultNetworkMigrationEnabled();
+    method public int getPathDegradationMigrationEnabled();
+    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
   }
 
-  public static class ConnectionMigrationOptions.Builder {
+  public static final class ConnectionMigrationOptions.Builder {
     ctor public ConnectionMigrationOptions.Builder();
-    method public android.net.http.ConnectionMigrationOptions build();
-    method public android.net.http.ConnectionMigrationOptions.Builder setAllowNonDefaultNetworkUsage(boolean);
-    method public android.net.http.ConnectionMigrationOptions.Builder setEnableDefaultNetworkMigration(boolean);
-    method public android.net.http.ConnectionMigrationOptions.Builder setEnablePathDegradationMigration(boolean);
+    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);
   }
 
   public final class DnsOptions {
-    method @Nullable public Boolean getEnableStaleDns();
-    method @Nullable public Boolean getPersistHostCache();
+    method public int getPersistHostCacheEnabled();
     method @Nullable public java.time.Duration getPersistHostCachePeriod();
-    method @Nullable public Boolean getPreestablishConnectionsToStaleDnsResults();
+    method public int getPreestablishConnectionsToStaleDnsResultsEnabled();
+    method public int getStaleDnsEnabled();
     method @Nullable public android.net.http.DnsOptions.StaleDnsOptions getStaleDnsOptions();
-    method @Nullable public Boolean getUseHttpStackDnsResolver();
+    method public int getUseHttpStackDnsResolverEnabled();
+    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
   }
 
   public static final class DnsOptions.Builder {
     ctor public DnsOptions.Builder();
-    method public android.net.http.DnsOptions build();
-    method public android.net.http.DnsOptions.Builder setEnableStaleDns(boolean);
-    method public android.net.http.DnsOptions.Builder setPersistHostCache(boolean);
-    method public android.net.http.DnsOptions.Builder setPersistHostCachePeriod(java.time.Duration);
-    method public android.net.http.DnsOptions.Builder setPreestablishConnectionsToStaleDnsResults(boolean);
-    method public android.net.http.DnsOptions.Builder setStaleDnsOptions(android.net.http.DnsOptions.StaleDnsOptions);
-    method public android.net.http.DnsOptions.Builder setUseHttpStackDnsResolver(boolean);
+    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 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 setStaleDnsOptions(@NonNull android.net.http.DnsOptions.StaleDnsOptions);
+    method @NonNull public android.net.http.DnsOptions.Builder setUseHttpStackDnsResolverEnabled(int);
   }
 
   public static class DnsOptions.StaleDnsOptions {
-    method @Nullable public Boolean getAllowCrossNetworkUsage();
-    method @Nullable public Long getFreshLookupTimeoutMillis();
-    method @Nullable public Long getMaxExpiredDelayMillis();
-    method @Nullable public Boolean getUseStaleOnNameNotResolved();
+    method public int getAllowCrossNetworkUsageEnabled();
+    method @Nullable public java.time.Duration getFreshLookupTimeout();
+    method @Nullable public java.time.Duration getMaxExpiredDelay();
+    method public int getUseStaleOnNameNotResolvedEnabled();
   }
 
   public static final class DnsOptions.StaleDnsOptions.Builder {
     ctor public DnsOptions.StaleDnsOptions.Builder();
-    method public android.net.http.DnsOptions.StaleDnsOptions build();
-    method public android.net.http.DnsOptions.StaleDnsOptions.Builder setAllowCrossNetworkUsage(boolean);
-    method public android.net.http.DnsOptions.StaleDnsOptions.Builder setFreshLookupTimeout(java.time.Duration);
-    method public android.net.http.DnsOptions.StaleDnsOptions.Builder setMaxExpiredDelay(java.time.Duration);
-    method public android.net.http.DnsOptions.StaleDnsOptions.Builder setUseStaleOnNameNotResolved(boolean);
+    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 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);
+  }
+
+  public abstract class HeaderBlock {
+    ctor public HeaderBlock();
+    method @NonNull public abstract java.util.List<java.util.Map.Entry<java.lang.String,java.lang.String>> getAsList();
+    method @NonNull public abstract java.util.Map<java.lang.String,java.util.List<java.lang.String>> getAsMap();
   }
 
   public abstract class HttpEngine {
     method public void bindToNetwork(@Nullable android.net.Network);
-    method public abstract java.net.URLStreamHandlerFactory createURLStreamHandlerFactory();
-    method public static String getVersionString();
-    method public abstract android.net.http.BidirectionalStream.Builder newBidirectionalStreamBuilder(String, android.net.http.BidirectionalStream.Callback, java.util.concurrent.Executor);
-    method public abstract android.net.http.UrlRequest.Builder newUrlRequestBuilder(String, android.net.http.UrlRequest.Callback, java.util.concurrent.Executor);
-    method public abstract java.net.URLConnection openConnection(java.net.URL) throws java.io.IOException;
+    method @NonNull public abstract java.net.URLStreamHandlerFactory createUrlStreamHandlerFactory();
+    method @NonNull public static String getVersionString();
+    method @NonNull public abstract android.net.http.BidirectionalStream.Builder newBidirectionalStreamBuilder(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.net.http.BidirectionalStream.Callback);
+    method @NonNull public abstract android.net.http.UrlRequest.Builder newUrlRequestBuilder(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.net.http.UrlRequest.Callback);
+    method @NonNull public abstract java.net.URLConnection openConnection(@NonNull java.net.URL) throws java.io.IOException;
     method public abstract void shutdown();
   }
 
   public static class HttpEngine.Builder {
-    ctor public HttpEngine.Builder(android.content.Context);
-    method public android.net.http.HttpEngine.Builder addPublicKeyPins(String, java.util.Set<byte[]>, boolean, java.time.Instant);
-    method public android.net.http.HttpEngine.Builder addQuicHint(String, int, int);
-    method public android.net.http.HttpEngine build();
-    method public String getDefaultUserAgent();
-    method public android.net.http.HttpEngine.Builder setConnectionMigrationOptions(android.net.http.ConnectionMigrationOptions);
-    method public android.net.http.HttpEngine.Builder setDnsOptions(android.net.http.DnsOptions);
-    method public android.net.http.HttpEngine.Builder setEnableBrotli(boolean);
-    method public android.net.http.HttpEngine.Builder setEnableHttp2(boolean);
-    method public android.net.http.HttpEngine.Builder setEnableHttpCache(int, long);
-    method public android.net.http.HttpEngine.Builder setEnablePublicKeyPinningBypassForLocalTrustAnchors(boolean);
-    method public android.net.http.HttpEngine.Builder setEnableQuic(boolean);
-    method public android.net.http.HttpEngine.Builder setQuicOptions(android.net.http.QuicOptions);
-    method public android.net.http.HttpEngine.Builder setStoragePath(String);
-    method public android.net.http.HttpEngine.Builder setUserAgent(String);
+    ctor public HttpEngine.Builder(@NonNull android.content.Context);
+    method @NonNull public android.net.http.HttpEngine.Builder addPublicKeyPins(@NonNull String, @NonNull java.util.Set<byte[]>, boolean, @NonNull java.time.Instant);
+    method @NonNull public android.net.http.HttpEngine.Builder addQuicHint(@NonNull String, int, int);
+    method @NonNull public android.net.http.HttpEngine build();
+    method @NonNull public String getDefaultUserAgent();
+    method @NonNull public android.net.http.HttpEngine.Builder setConnectionMigrationOptions(@NonNull android.net.http.ConnectionMigrationOptions);
+    method @NonNull public android.net.http.HttpEngine.Builder setDnsOptions(@NonNull android.net.http.DnsOptions);
+    method @NonNull public android.net.http.HttpEngine.Builder setEnableBrotli(boolean);
+    method @NonNull public android.net.http.HttpEngine.Builder setEnableHttp2(boolean);
+    method @NonNull public android.net.http.HttpEngine.Builder setEnableHttpCache(int, long);
+    method @NonNull public android.net.http.HttpEngine.Builder setEnablePublicKeyPinningBypassForLocalTrustAnchors(boolean);
+    method @NonNull public android.net.http.HttpEngine.Builder setEnableQuic(boolean);
+    method @NonNull public android.net.http.HttpEngine.Builder setQuicOptions(@NonNull android.net.http.QuicOptions);
+    method @NonNull public android.net.http.HttpEngine.Builder setStoragePath(@NonNull String);
+    method @NonNull public android.net.http.HttpEngine.Builder setUserAgent(@NonNull String);
     field public static final int HTTP_CACHE_DISABLED = 0; // 0x0
     field public static final int HTTP_CACHE_DISK = 3; // 0x3
     field public static final int HTTP_CACHE_DISK_NO_HTTP = 2; // 0x2
@@ -126,7 +145,7 @@
   }
 
   public class HttpException extends java.io.IOException {
-    ctor public HttpException(String, Throwable);
+    ctor public HttpException(@Nullable String, @Nullable Throwable);
   }
 
   public final class InlineExecutionProhibitedException extends java.util.concurrent.RejectedExecutionException {
@@ -134,7 +153,7 @@
   }
 
   public abstract class NetworkException extends android.net.http.HttpException {
-    ctor public NetworkException(String, Throwable);
+    ctor public NetworkException(@Nullable String, @Nullable Throwable);
     method public abstract int getErrorCode();
     method public abstract boolean isImmediatelyRetryable();
     field public static final int ERROR_ADDRESS_UNREACHABLE = 9; // 0x9
@@ -151,60 +170,57 @@
   }
 
   public abstract class QuicException extends android.net.http.NetworkException {
-    ctor protected QuicException(String, Throwable);
+    ctor protected QuicException(@Nullable String, @Nullable Throwable);
   }
 
   public class QuicOptions {
+    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 java.util.Set<java.lang.String> getQuicHostAllowlist();
   }
 
-  public static class QuicOptions.Builder {
+  public static final class QuicOptions.Builder {
     ctor public QuicOptions.Builder();
-    method public android.net.http.QuicOptions.Builder addAllowedQuicHost(String);
-    method public android.net.http.QuicOptions build();
-    method public android.net.http.QuicOptions.Builder setHandshakeUserAgent(String);
-    method public android.net.http.QuicOptions.Builder setIdleConnectionTimeout(java.time.Duration);
-    method public android.net.http.QuicOptions.Builder setInMemoryServerConfigsCacheSize(int);
+    method @NonNull public android.net.http.QuicOptions.Builder addAllowedQuicHost(@NonNull String);
+    method @NonNull public android.net.http.QuicOptions build();
+    method @NonNull public android.net.http.QuicOptions.Builder setHandshakeUserAgent(@NonNull String);
+    method @NonNull public android.net.http.QuicOptions.Builder setIdleConnectionTimeout(@NonNull java.time.Duration);
+    method @NonNull public android.net.http.QuicOptions.Builder setInMemoryServerConfigsCacheSize(int);
   }
 
   public abstract class UploadDataProvider implements java.io.Closeable {
     ctor public UploadDataProvider();
     method public void close() throws java.io.IOException;
     method public abstract long getLength() throws java.io.IOException;
-    method public abstract void read(android.net.http.UploadDataSink, java.nio.ByteBuffer) throws java.io.IOException;
-    method public abstract void rewind(android.net.http.UploadDataSink) throws java.io.IOException;
+    method public abstract void read(@NonNull android.net.http.UploadDataSink, @NonNull java.nio.ByteBuffer) throws java.io.IOException;
+    method public abstract void rewind(@NonNull android.net.http.UploadDataSink) throws java.io.IOException;
   }
 
   public abstract class UploadDataSink {
     ctor public UploadDataSink();
-    method public abstract void onReadError(Exception);
+    method public abstract void onReadError(@NonNull Exception);
     method public abstract void onReadSucceeded(boolean);
-    method public abstract void onRewindError(Exception);
+    method public abstract void onRewindError(@NonNull Exception);
     method public abstract void onRewindSucceeded();
   }
 
   public abstract class UrlRequest {
     method public abstract void cancel();
     method public abstract void followRedirect();
-    method public abstract void getStatus(android.net.http.UrlRequest.StatusListener);
+    method @NonNull public abstract android.net.http.HeaderBlock getHeaders();
+    method @Nullable public abstract String getHttpMethod();
+    method public abstract int getPriority();
+    method public abstract void getStatus(@NonNull android.net.http.UrlRequest.StatusListener);
+    method public abstract int getTrafficStatsTag();
+    method public abstract int getTrafficStatsUid();
+    method public abstract boolean hasTrafficStatsTag();
+    method public abstract boolean hasTrafficStatsUid();
+    method public abstract boolean isCacheDisabled();
+    method public abstract boolean isDirectExecutorAllowed();
     method public abstract boolean isDone();
-    method public abstract void read(java.nio.ByteBuffer);
+    method public abstract void read(@NonNull java.nio.ByteBuffer);
     method public abstract void start();
-  }
-
-  public abstract static class UrlRequest.Builder {
-    method public abstract android.net.http.UrlRequest.Builder addHeader(String, String);
-    method public abstract android.net.http.UrlRequest.Builder allowDirectExecutor();
-    method public abstract android.net.http.UrlRequest.Builder bindToNetwork(@Nullable android.net.Network);
-    method public abstract android.net.http.UrlRequest build();
-    method public abstract android.net.http.UrlRequest.Builder disableCache();
-    method public abstract android.net.http.UrlRequest.Builder setHttpMethod(String);
-    method public abstract android.net.http.UrlRequest.Builder setPriority(int);
-    method public abstract android.net.http.UrlRequest.Builder setTrafficStatsTag(int);
-    method public abstract android.net.http.UrlRequest.Builder setTrafficStatsUid(int);
-    method public abstract android.net.http.UrlRequest.Builder setUploadDataProvider(android.net.http.UploadDataProvider, java.util.concurrent.Executor);
     field public static final int REQUEST_PRIORITY_HIGHEST = 4; // 0x4
     field public static final int REQUEST_PRIORITY_IDLE = 0; // 0x0
     field public static final int REQUEST_PRIORITY_LOW = 2; // 0x2
@@ -212,14 +228,26 @@
     field public static final int REQUEST_PRIORITY_MEDIUM = 3; // 0x3
   }
 
-  public abstract static class UrlRequest.Callback {
-    ctor public UrlRequest.Callback();
-    method public void onCanceled(android.net.http.UrlRequest, android.net.http.UrlResponseInfo);
-    method public abstract void onFailed(android.net.http.UrlRequest, android.net.http.UrlResponseInfo, android.net.http.HttpException);
-    method public abstract void onReadCompleted(android.net.http.UrlRequest, android.net.http.UrlResponseInfo, java.nio.ByteBuffer) throws java.lang.Exception;
-    method public abstract void onRedirectReceived(android.net.http.UrlRequest, android.net.http.UrlResponseInfo, String) throws java.lang.Exception;
-    method public abstract void onResponseStarted(android.net.http.UrlRequest, android.net.http.UrlResponseInfo) throws java.lang.Exception;
-    method public abstract void onSucceeded(android.net.http.UrlRequest, android.net.http.UrlResponseInfo);
+  public abstract static class UrlRequest.Builder {
+    method @NonNull public abstract android.net.http.UrlRequest.Builder addHeader(@NonNull String, @NonNull String);
+    method @NonNull public abstract android.net.http.UrlRequest.Builder bindToNetwork(@Nullable android.net.Network);
+    method @NonNull public abstract android.net.http.UrlRequest build();
+    method @NonNull public abstract android.net.http.UrlRequest.Builder setCacheDisabled(boolean);
+    method @NonNull public abstract android.net.http.UrlRequest.Builder setDirectExecutorAllowed(boolean);
+    method @NonNull public abstract android.net.http.UrlRequest.Builder setHttpMethod(@NonNull String);
+    method @NonNull public abstract android.net.http.UrlRequest.Builder setPriority(int);
+    method @NonNull public abstract android.net.http.UrlRequest.Builder setTrafficStatsTag(int);
+    method @NonNull public abstract android.net.http.UrlRequest.Builder setTrafficStatsUid(int);
+    method @NonNull public abstract android.net.http.UrlRequest.Builder setUploadDataProvider(@NonNull android.net.http.UploadDataProvider, @NonNull java.util.concurrent.Executor);
+  }
+
+  public static interface UrlRequest.Callback {
+    method public void onCanceled(@NonNull android.net.http.UrlRequest, @Nullable android.net.http.UrlResponseInfo);
+    method public void onFailed(@NonNull android.net.http.UrlRequest, @Nullable android.net.http.UrlResponseInfo, @NonNull android.net.http.HttpException);
+    method public void onReadCompleted(@NonNull android.net.http.UrlRequest, @NonNull android.net.http.UrlResponseInfo, @NonNull java.nio.ByteBuffer) throws java.lang.Exception;
+    method public void onRedirectReceived(@NonNull android.net.http.UrlRequest, @NonNull android.net.http.UrlResponseInfo, @NonNull String) throws java.lang.Exception;
+    method public void onResponseStarted(@NonNull android.net.http.UrlRequest, @NonNull android.net.http.UrlResponseInfo) throws java.lang.Exception;
+    method public void onSucceeded(@NonNull android.net.http.UrlRequest, @NonNull android.net.http.UrlResponseInfo);
   }
 
   public static class UrlRequest.Status {
@@ -241,29 +269,21 @@
     field public static final int WAITING_FOR_STALLED_SOCKET_POOL = 1; // 0x1
   }
 
-  public abstract static class UrlRequest.StatusListener {
-    ctor public UrlRequest.StatusListener();
-    method public abstract void onStatus(int);
+  public static interface UrlRequest.StatusListener {
+    method public void onStatus(int);
   }
 
   public abstract class UrlResponseInfo {
     ctor public UrlResponseInfo();
-    method public abstract android.net.http.UrlResponseInfo.HeaderBlock getHeaders();
+    method @NonNull public abstract android.net.http.HeaderBlock getHeaders();
     method public abstract int getHttpStatusCode();
-    method public abstract String getHttpStatusText();
-    method public abstract String getNegotiatedProtocol();
-    method public abstract String getProxyServer();
+    method @NonNull public abstract String getHttpStatusText();
+    method @NonNull public abstract String getNegotiatedProtocol();
     method public abstract long getReceivedByteCount();
-    method public abstract String getUrl();
-    method public abstract java.util.List<java.lang.String> getUrlChain();
+    method @NonNull public abstract String getUrl();
+    method @NonNull public abstract java.util.List<java.lang.String> getUrlChain();
     method public abstract boolean wasCached();
   }
 
-  public abstract static class UrlResponseInfo.HeaderBlock {
-    ctor public UrlResponseInfo.HeaderBlock();
-    method public abstract java.util.List<java.util.Map.Entry<java.lang.String,java.lang.String>> getAsList();
-    method public abstract java.util.Map<java.lang.String,java.util.List<java.lang.String>> getAsMap();
-  }
-
 }
 
diff --git a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
index 9f8d9b1..976f5df 100644
--- a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
+++ b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
@@ -37,6 +37,7 @@
 
 import android.app.usage.NetworkStatsManager;
 import android.net.INetd;
+import android.net.LinkProperties;
 import android.net.MacAddress;
 import android.net.NetworkStats;
 import android.net.NetworkStats.Entry;
@@ -878,6 +879,27 @@
         return true;
     }
 
+    private int getMtu(@NonNull final String ifaceName, @NonNull final LinkProperties lp) {
+        int mtu = INVALID_MTU;
+
+        if (ifaceName.equals(lp.getInterfaceName())) {
+            mtu = lp.getMtu();
+        }
+
+        // Get mtu via kernel if mtu is not found in LinkProperties.
+        if (mtu == INVALID_MTU) {
+            mtu = mDeps.getNetworkInterfaceMtu(ifaceName);
+        }
+
+        // Use default mtu if can't find any.
+        if (mtu == INVALID_MTU) mtu = NetworkStackConstants.ETHER_MTU;
+
+        // Clamp to minimum ipv4 mtu
+        if (mtu < IPV4_MIN_MTU) mtu = IPV4_MIN_MTU;
+
+        return mtu;
+    }
+
     /**
      * Call when UpstreamNetworkState may be changed.
      * If upstream has ipv4 for tethering, update this new UpstreamNetworkState
@@ -900,16 +922,7 @@
             final String ifaceName = ns.linkProperties.getInterfaceName();
             final InterfaceParams params = mDeps.getInterfaceParams(ifaceName);
             final boolean isVcn = isVcnInterface(ifaceName);
-            mtu = ns.linkProperties.getMtu();
-            if (mtu == INVALID_MTU) {
-                // Get mtu via kernel if mtu is not found in LinkProperties.
-                mtu = mDeps.getNetworkInterfaceMtu(ifaceName);
-            }
-
-            // Use default mtu if can't find any.
-            if (mtu == INVALID_MTU) mtu = NetworkStackConstants.ETHER_MTU;
-            // Clamp to minimum ipv4 mtu
-            if (mtu < IPV4_MIN_MTU) mtu = IPV4_MIN_MTU;
+            mtu = getMtu(ifaceName, ns.linkProperties);
 
             if (!isVcn && params != null && !params.hasMacAddress /* raw ip upstream only */) {
                 upstreamIndex = params.index;
diff --git a/Tethering/tests/integration/base/android/net/EthernetTetheringTestBase.java b/Tethering/tests/integration/base/android/net/EthernetTetheringTestBase.java
index 7685981..69eb58f 100644
--- a/Tethering/tests/integration/base/android/net/EthernetTetheringTestBase.java
+++ b/Tethering/tests/integration/base/android/net/EthernetTetheringTestBase.java
@@ -72,6 +72,8 @@
 
 import com.android.modules.utils.build.SdkLevel;
 import com.android.net.module.util.PacketBuilder;
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.structs.Ipv6Header;
 import com.android.testutils.HandlerUtils;
 import com.android.testutils.TapPacketReader;
 import com.android.testutils.TestNetworkTracker;
@@ -251,6 +253,7 @@
             if (mRunTests) cleanUp();
         } finally {
             mHandlerThread.quitSafely();
+            mHandlerThread.join();
             mUiAutomation.dropShellPermissionIdentity();
         }
     }
@@ -1013,6 +1016,18 @@
         return new TetheringTester(mDownstreamReader, mUpstreamReader);
     }
 
+    @NonNull
+    protected Inet6Address getClatIpv6Address(TetheringTester tester, TetheredDevice tethered)
+            throws Exception {
+        // Send an IPv4 UDP packet from client and check that a CLAT translated IPv6 UDP packet can
+        // be found on upstream interface. Get CLAT IPv6 address from the CLAT translated IPv6 UDP
+        // packet.
+        byte[] expectedPacket = probeV4TetheringConnectivity(tester, tethered, true /* is4To6 */);
+
+        // Above has guaranteed that the found packet is an IPv6 packet without ether header.
+        return Struct.parse(Ipv6Header.class, ByteBuffer.wrap(expectedPacket)).srcIp;
+    }
+
     protected <T> List<T> toList(T... array) {
         return Arrays.asList(array);
     }
diff --git a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
index fb4b9fa..12ac454 100644
--- a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
+++ b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
@@ -62,7 +62,6 @@
 import com.android.net.module.util.structs.EthernetHeader;
 import com.android.net.module.util.structs.Icmpv4Header;
 import com.android.net.module.util.structs.Ipv4Header;
-import com.android.net.module.util.structs.Ipv6Header;
 import com.android.net.module.util.structs.UdpHeader;
 import com.android.testutils.DevSdkIgnoreRule;
 import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
@@ -522,18 +521,6 @@
         runUdp4Test();
     }
 
-    @NonNull
-    private Inet6Address getClatIpv6Address(TetheringTester tester, TetheredDevice tethered)
-            throws Exception {
-        // Send an IPv4 UDP packet from client and check that a CLAT translated IPv6 UDP packet can
-        // be found on upstream interface. Get CLAT IPv6 address from the CLAT translated IPv6 UDP
-        // packet.
-        byte[] expectedPacket = probeV4TetheringConnectivity(tester, tethered, true /* is4To6 */);
-
-        // Above has guaranteed that the found packet is an IPv6 packet without ether header.
-        return Struct.parse(Ipv6Header.class, ByteBuffer.wrap(expectedPacket)).srcIp;
-    }
-
     // Test network topology:
     //
     //            public network (rawip)                 private network
diff --git a/Tethering/tests/mts/AndroidManifest.xml b/Tethering/tests/mts/AndroidManifest.xml
index 6d2abca..42f2da9 100644
--- a/Tethering/tests/mts/AndroidManifest.xml
+++ b/Tethering/tests/mts/AndroidManifest.xml
@@ -27,8 +27,6 @@
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
                      android:targetPackage="android.tethering.mts"
                      android:label="MTS tests of android.tethering">
-        <meta-data android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener" />
     </instrumentation>
 
 </manifest>
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/metrics/TetheringMetricsTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/metrics/TetheringMetricsTest.java
index 7fdde97..98c873d 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/metrics/TetheringMetricsTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/metrics/TetheringMetricsTest.java
@@ -48,7 +48,6 @@
 import android.stats.connectivity.ErrorCode;
 import android.stats.connectivity.UpstreamType;
 import android.stats.connectivity.UserType;
-import android.util.Pair;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -99,86 +98,70 @@
         mTetheringMetrics.sendReport(downstream);
     }
 
-    private void runDownstreamTypesTest(final Pair<Integer, DownstreamType>... testPairs)
+    private void runDownstreamTypesTest(final int type, final DownstreamType expectedResult)
             throws Exception {
-        for (Pair<Integer, DownstreamType> testPair : testPairs) {
-            final int type = testPair.first;
-            final DownstreamType expectedResult = testPair.second;
-
-            mTetheringMetrics.createBuilder(type, TEST_CALLER_PKG);
-            updateErrorAndSendReport(type, TETHER_ERROR_NO_ERROR);
-            verifyReport(expectedResult, ErrorCode.EC_NO_ERROR, UserType.USER_UNKNOWN);
-            reset(mTetheringMetrics);
-        }
+        mTetheringMetrics.createBuilder(type, TEST_CALLER_PKG);
+        updateErrorAndSendReport(type, TETHER_ERROR_NO_ERROR);
+        verifyReport(expectedResult, ErrorCode.EC_NO_ERROR, UserType.USER_UNKNOWN);
+        reset(mTetheringMetrics);
     }
 
     @Test
     public void testDownstreamTypes() throws Exception {
-        runDownstreamTypesTest(new Pair<>(TETHERING_WIFI, DownstreamType.DS_TETHERING_WIFI),
-                new Pair<>(TETHERING_WIFI_P2P, DownstreamType.DS_TETHERING_WIFI_P2P),
-                new Pair<>(TETHERING_BLUETOOTH, DownstreamType.DS_TETHERING_BLUETOOTH),
-                new Pair<>(TETHERING_USB, DownstreamType.DS_TETHERING_USB),
-                new Pair<>(TETHERING_NCM, DownstreamType.DS_TETHERING_NCM),
-                new Pair<>(TETHERING_ETHERNET, DownstreamType.DS_TETHERING_ETHERNET));
+        runDownstreamTypesTest(TETHERING_WIFI, DownstreamType.DS_TETHERING_WIFI);
+        runDownstreamTypesTest(TETHERING_WIFI_P2P, DownstreamType.DS_TETHERING_WIFI_P2P);
+        runDownstreamTypesTest(TETHERING_BLUETOOTH, DownstreamType.DS_TETHERING_BLUETOOTH);
+        runDownstreamTypesTest(TETHERING_USB, DownstreamType.DS_TETHERING_USB);
+        runDownstreamTypesTest(TETHERING_NCM, DownstreamType.DS_TETHERING_NCM);
+        runDownstreamTypesTest(TETHERING_ETHERNET, DownstreamType.DS_TETHERING_ETHERNET);
     }
 
-    private void runErrorCodesTest(final Pair<Integer, ErrorCode>... testPairs)
+    private void runErrorCodesTest(final int errorCode, final ErrorCode expectedResult)
             throws Exception {
-        for (Pair<Integer, ErrorCode> testPair : testPairs) {
-            final int errorCode = testPair.first;
-            final ErrorCode expectedResult = testPair.second;
-
-            mTetheringMetrics.createBuilder(TETHERING_WIFI, TEST_CALLER_PKG);
-            updateErrorAndSendReport(TETHERING_WIFI, errorCode);
-            verifyReport(DownstreamType.DS_TETHERING_WIFI, expectedResult, UserType.USER_UNKNOWN);
-            reset(mTetheringMetrics);
-        }
+        mTetheringMetrics.createBuilder(TETHERING_WIFI, TEST_CALLER_PKG);
+        updateErrorAndSendReport(TETHERING_WIFI, errorCode);
+        verifyReport(DownstreamType.DS_TETHERING_WIFI, expectedResult, UserType.USER_UNKNOWN);
     }
 
     @Test
     public void testErrorCodes() throws Exception {
-        runErrorCodesTest(new Pair<>(TETHER_ERROR_NO_ERROR, ErrorCode.EC_NO_ERROR),
-                new Pair<>(TETHER_ERROR_UNKNOWN_IFACE, ErrorCode.EC_UNKNOWN_IFACE),
-                new Pair<>(TETHER_ERROR_SERVICE_UNAVAIL, ErrorCode.EC_SERVICE_UNAVAIL),
-                new Pair<>(TETHER_ERROR_UNSUPPORTED, ErrorCode.EC_UNSUPPORTED),
-                new Pair<>(TETHER_ERROR_UNAVAIL_IFACE, ErrorCode.EC_UNAVAIL_IFACE),
-                new Pair<>(TETHER_ERROR_INTERNAL_ERROR, ErrorCode.EC_INTERNAL_ERROR),
-                new Pair<>(TETHER_ERROR_TETHER_IFACE_ERROR, ErrorCode.EC_TETHER_IFACE_ERROR),
-                new Pair<>(TETHER_ERROR_UNTETHER_IFACE_ERROR, ErrorCode.EC_UNTETHER_IFACE_ERROR),
-                new Pair<>(TETHER_ERROR_ENABLE_FORWARDING_ERROR,
-                ErrorCode.EC_ENABLE_FORWARDING_ERROR),
-                new Pair<>(TETHER_ERROR_DISABLE_FORWARDING_ERROR,
-                ErrorCode.EC_DISABLE_FORWARDING_ERROR),
-                new Pair<>(TETHER_ERROR_IFACE_CFG_ERROR, ErrorCode.EC_IFACE_CFG_ERROR),
-                new Pair<>(TETHER_ERROR_PROVISIONING_FAILED, ErrorCode.EC_PROVISIONING_FAILED),
-                new Pair<>(TETHER_ERROR_DHCPSERVER_ERROR, ErrorCode.EC_DHCPSERVER_ERROR),
-                new Pair<>(TETHER_ERROR_ENTITLEMENT_UNKNOWN, ErrorCode.EC_ENTITLEMENT_UNKNOWN),
-                new Pair<>(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION,
-                ErrorCode.EC_NO_CHANGE_TETHERING_PERMISSION),
-                new Pair<>(TETHER_ERROR_NO_ACCESS_TETHERING_PERMISSION,
-                ErrorCode.EC_NO_ACCESS_TETHERING_PERMISSION),
-                new Pair<>(TETHER_ERROR_UNKNOWN_TYPE, ErrorCode.EC_UNKNOWN_TYPE));
+        runErrorCodesTest(TETHER_ERROR_NO_ERROR, ErrorCode.EC_NO_ERROR);
+        runErrorCodesTest(TETHER_ERROR_UNKNOWN_IFACE, ErrorCode.EC_UNKNOWN_IFACE);
+        runErrorCodesTest(TETHER_ERROR_SERVICE_UNAVAIL, ErrorCode.EC_SERVICE_UNAVAIL);
+        runErrorCodesTest(TETHER_ERROR_UNSUPPORTED, ErrorCode.EC_UNSUPPORTED);
+        runErrorCodesTest(TETHER_ERROR_UNAVAIL_IFACE, ErrorCode.EC_UNAVAIL_IFACE);
+        runErrorCodesTest(TETHER_ERROR_INTERNAL_ERROR, ErrorCode.EC_INTERNAL_ERROR);
+        runErrorCodesTest(TETHER_ERROR_TETHER_IFACE_ERROR, ErrorCode.EC_TETHER_IFACE_ERROR);
+        runErrorCodesTest(TETHER_ERROR_UNTETHER_IFACE_ERROR, ErrorCode.EC_UNTETHER_IFACE_ERROR);
+        runErrorCodesTest(TETHER_ERROR_ENABLE_FORWARDING_ERROR,
+                ErrorCode.EC_ENABLE_FORWARDING_ERROR);
+        runErrorCodesTest(TETHER_ERROR_DISABLE_FORWARDING_ERROR,
+                ErrorCode.EC_DISABLE_FORWARDING_ERROR);
+        runErrorCodesTest(TETHER_ERROR_IFACE_CFG_ERROR, ErrorCode.EC_IFACE_CFG_ERROR);
+        runErrorCodesTest(TETHER_ERROR_PROVISIONING_FAILED, ErrorCode.EC_PROVISIONING_FAILED);
+        runErrorCodesTest(TETHER_ERROR_DHCPSERVER_ERROR, ErrorCode.EC_DHCPSERVER_ERROR);
+        runErrorCodesTest(TETHER_ERROR_ENTITLEMENT_UNKNOWN, ErrorCode.EC_ENTITLEMENT_UNKNOWN);
+        runErrorCodesTest(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION,
+                ErrorCode.EC_NO_CHANGE_TETHERING_PERMISSION);
+        runErrorCodesTest(TETHER_ERROR_NO_ACCESS_TETHERING_PERMISSION,
+                ErrorCode.EC_NO_ACCESS_TETHERING_PERMISSION);
+        runErrorCodesTest(TETHER_ERROR_UNKNOWN_TYPE, ErrorCode.EC_UNKNOWN_TYPE);
     }
 
-    private void runUserTypesTest(final Pair<String, UserType>... testPairs)
+    private void runUserTypesTest(final String callerPkg, final UserType expectedResult)
             throws Exception {
-        for (Pair<String, UserType> testPair : testPairs) {
-            final String callerPkg = testPair.first;
-            final UserType expectedResult = testPair.second;
-
-            mTetheringMetrics.createBuilder(TETHERING_WIFI, callerPkg);
-            updateErrorAndSendReport(TETHERING_WIFI, TETHER_ERROR_NO_ERROR);
-            verifyReport(DownstreamType.DS_TETHERING_WIFI, ErrorCode.EC_NO_ERROR, expectedResult);
-            reset(mTetheringMetrics);
-        }
+        mTetheringMetrics.createBuilder(TETHERING_WIFI, callerPkg);
+        updateErrorAndSendReport(TETHERING_WIFI, TETHER_ERROR_NO_ERROR);
+        verifyReport(DownstreamType.DS_TETHERING_WIFI, ErrorCode.EC_NO_ERROR, expectedResult);
+        reset(mTetheringMetrics);
     }
 
     @Test
     public void testUserTypes() throws Exception {
-        runUserTypesTest(new Pair<>(TEST_CALLER_PKG, UserType.USER_UNKNOWN),
-                new Pair<>(SETTINGS_PKG, UserType.USER_SETTINGS),
-                new Pair<>(SYSTEMUI_PKG, UserType.USER_SYSTEMUI),
-                new Pair<>(GMS_PKG, UserType.USER_GMS));
+        runUserTypesTest(TEST_CALLER_PKG, UserType.USER_UNKNOWN);
+        runUserTypesTest(SETTINGS_PKG, UserType.USER_SETTINGS);
+        runUserTypesTest(SYSTEMUI_PKG, UserType.USER_SYSTEMUI);
+        runUserTypesTest(GMS_PKG, UserType.USER_GMS);
     }
 
     @Test
diff --git a/bpf_progs/netd.c b/bpf_progs/netd.c
index 84da79d..e068d8a 100644
--- a/bpf_progs/netd.c
+++ b/bpf_progs/netd.c
@@ -350,10 +350,10 @@
 
 static __always_inline inline int bpf_owner_match(struct __sk_buff* skb, uint32_t uid,
                                                   bool egress, const unsigned kver) {
-    if (skip_owner_match(skb, kver)) return PASS;
-
     if (is_system_uid(uid)) return PASS;
 
+    if (skip_owner_match(skb, kver)) return PASS;
+
     BpfConfig enabledRules = getConfig(UID_RULES_CONFIGURATION_KEY);
 
     UidOwnerValue* uidEntry = bpf_uid_owner_map_lookup_elem(&uid);
@@ -415,11 +415,6 @@
     }
 
     int match = bpf_owner_match(skb, sock_uid, egress, kver);
-    if (egress && (match == DROP)) {
-        // If an outbound packet is going to be dropped, we do not count that
-        // traffic.
-        return match;
-    }
 
 // Workaround for secureVPN with VpnIsolation enabled, refer to b/159994981 for details.
 // Keep TAG_SYSTEM_DNS in sync with DnsResolver/include/netd_resolv/resolv.h
@@ -432,6 +427,9 @@
         if (match == DROP_UNLESS_DNS) match = DROP;
     }
 
+    // If an outbound packet is going to be dropped, we do not count that traffic.
+    if (egress && (match == DROP)) return DROP;
+
     StatsKey key = {.uid = uid, .tag = tag, .counterSet = 0, .ifaceIndex = skb->ifindex};
 
     uint8_t* counterSet = bpf_uid_counterset_map_lookup_elem(&uid);
diff --git a/framework-t/Android.bp b/framework-t/Android.bp
index 7ef20c5..1a8d46b 100644
--- a/framework-t/Android.bp
+++ b/framework-t/Android.bp
@@ -156,7 +156,7 @@
         "//frameworks/opt/telephony/tests/telephonytests",
         "//packages/modules/CaptivePortalLogin/tests",
         "//packages/modules/Connectivity/Tethering/tests:__subpackages__",
-        "//packages/modules/Connectivity/nearby/tests:__subpackages__",
+        "//packages/modules/Connectivity/nearby:__subpackages__",
         "//packages/modules/Connectivity/tests:__subpackages__",
         "//packages/modules/IPsec/tests/iketests",
         "//packages/modules/NetworkStack/tests:__subpackages__",
diff --git a/framework-t/src/android/net/NetworkIdentity.java b/framework-t/src/android/net/NetworkIdentity.java
index edfd21c..947a092 100644
--- a/framework-t/src/android/net/NetworkIdentity.java
+++ b/framework-t/src/android/net/NetworkIdentity.java
@@ -32,6 +32,7 @@
 import android.net.wifi.WifiInfo;
 import android.service.NetworkIdentityProto;
 import android.telephony.TelephonyManager;
+import android.util.Log;
 import android.util.proto.ProtoOutputStream;
 
 import com.android.net.module.util.BitUtils;
@@ -406,10 +407,18 @@
             setOemManaged(getOemBitfield(snapshot.getNetworkCapabilities()));
 
             if (mType == TYPE_WIFI) {
-                final TransportInfo transportInfo = snapshot.getNetworkCapabilities()
-                        .getTransportInfo();
+                final NetworkCapabilities nc = snapshot.getNetworkCapabilities();
+                final TransportInfo transportInfo = nc.getTransportInfo();
                 if (transportInfo instanceof WifiInfo) {
                     final WifiInfo info = (WifiInfo) transportInfo;
+                    // Log.wtf to catch trying to set a null wifiNetworkKey into NetworkIdentity.
+                    // See b/266598304. The problematic data that has null wifi network key is
+                    // thrown out when storing data, which is handled by the service.
+                    if (info.getNetworkKey() == null) {
+                        Log.wtf(TAG, "WifiInfo contains a null wifiNetworkKey and it will"
+                                + " be set into NetworkIdentity, netId=" + snapshot.getNetwork()
+                                + "NetworkCapabilities=" + nc);
+                    }
                     setWifiNetworkKey(info.getNetworkKey());
                 }
             } else if (mType == TYPE_TEST) {
diff --git a/framework-t/src/android/net/NetworkStatsAccess.java b/framework-t/src/android/net/NetworkStatsAccess.java
index b64fbdb..0585756 100644
--- a/framework-t/src/android/net/NetworkStatsAccess.java
+++ b/framework-t/src/android/net/NetworkStatsAccess.java
@@ -111,17 +111,18 @@
         final DevicePolicyManager mDpm = context.getSystemService(DevicePolicyManager.class);
         final TelephonyManager tm = (TelephonyManager)
                 context.getSystemService(Context.TELEPHONY_SERVICE);
-        boolean hasCarrierPrivileges;
-        final long token = Binder.clearCallingIdentity();
+        final boolean hasCarrierPrivileges;
+        final boolean isDeviceOwner;
+        long token = Binder.clearCallingIdentity();
         try {
             hasCarrierPrivileges = tm != null
                     && tm.checkCarrierPrivilegesForPackageAnyPhone(callingPackage)
                             == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS;
+            isDeviceOwner = mDpm != null && mDpm.isDeviceOwnerApp(callingPackage);
         } finally {
             Binder.restoreCallingIdentity(token);
         }
 
-        final boolean isDeviceOwner = mDpm != null && mDpm.isDeviceOwnerApp(callingPackage);
         final int appId = UserHandle.getAppId(callingUid);
 
         final boolean isNetworkStack = context.checkPermission(
@@ -135,15 +136,20 @@
             return NetworkStatsAccess.Level.DEVICE;
         }
 
-        boolean hasAppOpsPermission = hasAppOpsPermission(context, callingUid, callingPackage);
+        final boolean hasAppOpsPermission =
+                hasAppOpsPermission(context, callingUid, callingPackage);
         if (hasAppOpsPermission || context.checkCallingOrSelfPermission(
                 READ_NETWORK_USAGE_HISTORY) == PackageManager.PERMISSION_GRANTED) {
             return NetworkStatsAccess.Level.DEVICESUMMARY;
         }
 
-        //TODO(b/169395065) Figure out if this flow makes sense in Device Owner mode.
-        boolean isProfileOwner = mDpm != null && (mDpm.isProfileOwnerApp(callingPackage)
-                || mDpm.isDeviceOwnerApp(callingPackage));
+        final boolean isProfileOwner;
+        token = Binder.clearCallingIdentity();
+        try {
+            isProfileOwner = mDpm != null && mDpm.isProfileOwnerApp(callingPackage);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
         if (isProfileOwner) {
             // Apps with the AppOps permission, profile owners, and apps with the privileged
             // permission can access data usage for all apps in this user/profile.
diff --git a/framework-t/src/android/net/NetworkTemplate.java b/framework-t/src/android/net/NetworkTemplate.java
index 4a1632b..d90bd8d 100644
--- a/framework-t/src/android/net/NetworkTemplate.java
+++ b/framework-t/src/android/net/NetworkTemplate.java
@@ -51,6 +51,7 @@
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.modules.utils.build.SdkLevel;
 import com.android.net.module.util.CollectionUtils;
 import com.android.net.module.util.NetworkIdentityUtils;
 
@@ -246,6 +247,38 @@
         return new NetworkTemplate.Builder(MATCH_ETHERNET).build();
     }
 
+    /**
+     * Template to combine all {@link ConnectivityManager#TYPE_BLUETOOTH} style
+     * networks together.
+     *
+     * @hide
+     */
+    // TODO(b/270089918): Remove this method. This can only be done after there are no more callers,
+    //  including in OEM code which can access this by linking against the framework.
+    public static NetworkTemplate buildTemplateBluetooth() {
+        if (SdkLevel.isAtLeastU()) {
+            throw new UnsupportedOperationException(
+                    "buildTemplateBluetooth is not supported on Android U devices or above");
+        }
+        return new NetworkTemplate.Builder(MATCH_BLUETOOTH).build();
+    }
+
+    /**
+     * Template to combine all {@link ConnectivityManager#TYPE_PROXY} style
+     * networks together.
+     *
+     * @hide
+     */
+    // TODO(b/270089918): Remove this method. This can only be done after there are no more callers,
+    //  including in OEM code which can access this by linking against the framework.
+    public static NetworkTemplate buildTemplateProxy() {
+        if (SdkLevel.isAtLeastU()) {
+            throw new UnsupportedOperationException(
+                    "buildTemplateProxy is not supported on Android U devices or above");
+        }
+        return new NetworkTemplate(MATCH_PROXY, null, null);
+    }
+
     private final int mMatchRule;
 
     /**
@@ -316,6 +349,10 @@
         if (matchRule == 6 || matchRule == 7) {
             Log.e(TAG, "Use MATCH_MOBILE with empty subscriberIds or MATCH_WIFI with empty "
                     + "wifiNetworkKeys instead of template with matchRule=" + matchRule);
+            if (SdkLevel.isAtLeastU()) {
+                throw new UnsupportedOperationException(
+                        "Wildcard templates are not supported on Android U devices or above");
+            }
         }
     }
 
@@ -337,6 +374,43 @@
     }
 
     /** @hide */
+    // TODO(b/270089918): Remove this method after no callers.
+    public NetworkTemplate(int matchRule, String subscriberId, String[] matchSubscriberIds,
+            String wifiNetworkKey) {
+        // Older versions used to only match MATCH_MOBILE and MATCH_MOBILE_WILDCARD templates
+        // to metered networks. It is now possible to match mobile with any meteredness, but
+        // in order to preserve backward compatibility of @UnsupportedAppUsage methods, this
+        // constructor passes METERED_YES for these types.
+        this(getBackwardsCompatibleMatchRule(matchRule), matchSubscriberIds,
+                wifiNetworkKey != null ? new String[] { wifiNetworkKey } : new String[0],
+                getMeterednessForBackwardsCompatibility(matchRule),
+                ROAMING_ALL, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL,
+                OEM_MANAGED_ALL);
+        if (SdkLevel.isAtLeastU()) {
+            throw new UnsupportedOperationException(
+                    "This constructor is not supported on Android U devices or above");
+        }
+    }
+
+    /** @hide */
+    // TODO(b/269974916): Remove this method after Android U is released.
+    //  This is only used by CTS of Android T.
+    public NetworkTemplate(int matchRule, String subscriberId, String[] matchSubscriberIds,
+            String[] matchWifiNetworkKeys, int metered, int roaming,
+            int defaultNetwork, int ratType, int oemManaged, int subscriberIdMatchRule) {
+        // subscriberId and subscriberIdMatchRule aren't used since they are replaced by
+        // matchSubscriberIds, which could be null to indicate the intention of matching any
+        // subscriberIds.
+        this(getBackwardsCompatibleMatchRule(matchRule),
+                matchSubscriberIds == null ? new String[]{} : matchSubscriberIds,
+                matchWifiNetworkKeys, metered, roaming, defaultNetwork, ratType, oemManaged);
+        if (SdkLevel.isAtLeastU()) {
+            throw new UnsupportedOperationException(
+                    "This constructor is not supported on Android U devices or above");
+        }
+    }
+
+    /** @hide */
     public NetworkTemplate(int matchRule, String[] matchSubscriberIds,
             String[] matchWifiNetworkKeys, int metered, int roaming, int defaultNetwork,
             int ratType, int oemManaged) {
@@ -436,9 +510,14 @@
         return false;
     }
 
-    // TODO(b/270089918): Remove this method after no callers.
+    // TODO(b/270089918): Remove this method. This can only be done after there are no more callers,
+    //  including in OEM code which can access this by linking against the framework.
     /** @hide */
     public boolean isMatchRuleMobile() {
+        if (SdkLevel.isAtLeastU()) {
+            throw new UnsupportedOperationException(
+                    "isMatchRuleMobile is not supported on Android U devices or above");
+        }
         switch (mMatchRule) {
             case MATCH_MOBILE:
             // Old MATCH_MOBILE_WILDCARD
@@ -621,7 +700,15 @@
      *                  to know details about the key.
      */
     private boolean matchesWifiNetworkKey(@NonNull String wifiNetworkKey) {
-        Objects.requireNonNull(wifiNetworkKey);
+        // Note that this code accepts null wifi network keys because of a past bug where wifi
+        // code was sending a null network key for some connected networks, which isn't expected
+        // and ended up stored in the data on many devices.
+        // A null network key in the data matches a wildcard template (one where
+        // {@code mMatchWifiNetworkKeys} is empty), but not one where {@code MatchWifiNetworkKeys}
+        // contains null. See b/266598304.
+        if (wifiNetworkKey == null) {
+            return CollectionUtils.isEmpty(mMatchWifiNetworkKeys);
+        }
         return CollectionUtils.isEmpty(mMatchWifiNetworkKeys)
                 || CollectionUtils.contains(mMatchWifiNetworkKeys, wifiNetworkKey);
     }
diff --git a/framework-t/src/android/net/nsd/NsdManager.java b/framework-t/src/android/net/nsd/NsdManager.java
index b4f8897..36808cf 100644
--- a/framework-t/src/android/net/nsd/NsdManager.java
+++ b/framework-t/src/android/net/nsd/NsdManager.java
@@ -1312,7 +1312,7 @@
      * before registering other callbacks. Upon failure to register a callback for example if
      * it's a duplicated registration, the application is notified through
      * {@link ServiceInfoCallback#onServiceInfoCallbackRegistrationFailed} with
-     * {@link #FAILURE_BAD_PARAMETERS} or {@link #FAILURE_ALREADY_ACTIVE}.
+     * {@link #FAILURE_BAD_PARAMETERS}.
      *
      * @param serviceInfo the service to receive updates for
      * @param executor Executor to run callbacks with
diff --git a/framework/api/current.txt b/framework/api/current.txt
index 547b7e2..672e3e2 100644
--- a/framework/api/current.txt
+++ b/framework/api/current.txt
@@ -360,6 +360,7 @@
     field public static final int TRANSPORT_CELLULAR = 0; // 0x0
     field public static final int TRANSPORT_ETHERNET = 3; // 0x3
     field public static final int TRANSPORT_LOWPAN = 6; // 0x6
+    field public static final int TRANSPORT_THREAD = 9; // 0x9
     field public static final int TRANSPORT_USB = 8; // 0x8
     field public static final int TRANSPORT_VPN = 4; // 0x4
     field public static final int TRANSPORT_WIFI = 1; // 0x1
diff --git a/framework/api/system-current.txt b/framework/api/system-current.txt
index 196e023..4a2ed8a 100644
--- a/framework/api/system-current.txt
+++ b/framework/api/system-current.txt
@@ -470,7 +470,7 @@
   }
 
   public abstract class SocketKeepalive implements java.lang.AutoCloseable {
-    method public final void start(@IntRange(from=0xa, to=0xe10) int, int, @NonNull android.net.Network);
+    method public final void start(@IntRange(from=0xa, to=0xe10) int, int, @Nullable android.net.Network);
     field public static final int ERROR_NO_SUCH_SLOT = -33; // 0xffffffdf
     field public static final int FLAG_AUTOMATIC_ON_OFF = 1; // 0x1
     field public static final int SUCCESS = 0; // 0x0
diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java
index 7cef58b..381a18a 100644
--- a/framework/src/android/net/ConnectivityManager.java
+++ b/framework/src/android/net/ConnectivityManager.java
@@ -2521,7 +2521,7 @@
     @RequiresPermission(android.Manifest.permission.PACKET_KEEPALIVE_OFFLOAD)
     public @NonNull SocketKeepalive createSocketKeepalive(@NonNull Network network,
             @NonNull Socket socket,
-            @NonNull Executor executor,
+            @NonNull @CallbackExecutor Executor executor,
             @NonNull Callback callback) {
         ParcelFileDescriptor dup;
         try {
@@ -5494,9 +5494,9 @@
      * @return {@code uid} if the connection is found and the app has permission to observe it
      *     (e.g., if it is associated with the calling VPN app's VpnService tunnel) or {@link
      *     android.os.Process#INVALID_UID} if the connection is not found.
-     * @throws {@link SecurityException} if the caller is not the active VpnService for the current
+     * @throws SecurityException if the caller is not the active VpnService for the current
      *     user.
-     * @throws {@link IllegalArgumentException} if an unsupported protocol is requested.
+     * @throws IllegalArgumentException if an unsupported protocol is requested.
      */
     public int getConnectionOwnerUid(
             int protocol, @NonNull InetSocketAddress local, @NonNull InetSocketAddress remote) {
diff --git a/framework/src/android/net/NetworkCapabilities.java b/framework/src/android/net/NetworkCapabilities.java
index e70d75d..324f565 100644
--- a/framework/src/android/net/NetworkCapabilities.java
+++ b/framework/src/android/net/NetworkCapabilities.java
@@ -53,18 +53,70 @@
 import java.util.StringJoiner;
 
 /**
- * Representation of the capabilities of an active network. Instances are
- * typically obtained through
- * {@link NetworkCallback#onCapabilitiesChanged(Network, NetworkCapabilities)}
- * or {@link ConnectivityManager#getNetworkCapabilities(Network)}.
- * <p>
- * This replaces the old {@link ConnectivityManager#TYPE_MOBILE} method of
- * network selection. Rather than indicate a need for Wi-Fi because an
- * application needs high bandwidth and risk obsolescence when a new, fast
- * network appears (like LTE), the application should specify it needs high
- * bandwidth. Similarly if an application needs an unmetered network for a bulk
- * transfer it can specify that rather than assuming all cellular based
- * connections are metered and all Wi-Fi based connections are not.
+ * Representation of the capabilities of an active network.
+ *
+ * <p>@see <a href="https://developer.android.com/training/basics/network-ops/reading-network-state>
+ * this general guide</a> on how to use NetworkCapabilities and related classes.
+ *
+ * <p>NetworkCapabilities represent what a network can do and what its
+ * characteristics are like. The principal attribute of NetworkCapabilities
+ * is in the capabilities bits, which are checked with
+ * {@link #hasCapability(int)}. See the list of capabilities and each
+ * capability for a description of what it means.
+ *
+ * <p>Some prime examples include {@code NET_CAPABILITY_MMS}, which means that the
+ * network is capable of sending MMS. A network without this capability
+ * is not capable of sending MMS.
+ * <p>The {@code NET_CAPABILITY_INTERNET} capability means that the network is
+ * configured to reach the general Internet. It may or may not actually
+ * provide connectivity ; the {@code NET_CAPABILITY_VALIDATED} bit indicates that
+ * the system found actual connectivity to the general Internet the last
+ * time it checked. Apps interested in actual connectivity should usually
+ * look at both these capabilities.
+ * <p>The {@code NET_CAPABILITY_NOT_METERED} capability is set for networks that
+ * do not bill the user for consumption of bytes. Applications are
+ * encouraged to consult this to determine appropriate usage, and to
+ * limit usage of metered network where possible, including deferring
+ * big downloads until such a time that an unmetered network is connected.
+ * Also see {@link android.app.job.JobScheduler} to help with scheduling such
+ * downloads, in particular
+ * {@link android.app.job.JobInfo.Builder#setRequiredNetwork(NetworkRequest)}.
+ * <p>NetworkCapabilities contain a number of other capabilities that
+ * represent what modern networks can and can't do. Look up the individual
+ * capabilities in this class to learn about each of them.
+ *
+ * <p>NetworkCapabilities typically represent attributes that can apply to
+ * any network. The attributes that apply only to specific transports like
+ * cellular or Wi-Fi can be found in the specifier (for requestable attributes)
+ * or in the transport info (for non-requestable ones). See
+ * {@link #getNetworkSpecifier} and {@link #getTransportInfo}. An app would
+ * downcast these to the specific class for the transport they need if they
+ * are interested in transport-specific attributes. Also see
+ * {@link android.net.wifi.WifiNetworkSpecifier} or
+ * {@link android.net.wifi.WifiInfo} for some examples of each of these.
+ *
+ * <p>NetworkCapabilities also contains other attributes like the estimated
+ * upstream and downstream bandwidth and the specific transport of that
+ * network (e.g. {@link #TRANSPORT_CELLULAR}). Generally, apps should normally
+ * have little reason to check for the type of transport ; for example, to
+ * query whether a network costs money to the user, do not look at the
+ * transport, but instead look at the absence or presence of
+ * {@link #NET_CAPABILITY_NOT_METERED} which will correctly account for
+ * metered Wi-Fis and free of charge cell connections.
+ *
+ * <p>The system communicates with apps about connected networks and uses
+ * NetworkCapabilities to express these capabilities about these networks.
+ * Apps should register callbacks with the {@link ConnectivityManager#requestNetwork}
+ * or {@link ConnectivityManager#registerNetworkCallback} family of methods
+ * to learn about the capabilities of a network on a continuous basis
+ * and be able to react to changes to capabilities. For quick debugging Android also
+ * provides {@link ConnectivityManager#getNetworkCapabilities(Network)},
+ * but the dynamic nature of networking makes this ill-suited to production
+ * code since capabilities obtained in this way can go stale immediately.
+ *
+ * <p>Also see {@link NetworkRequest} which uses the same capabilities
+ * together with {@link ConnectivityManager#requestNetwork} for how to
+ * request the system brings up the kind of network your application needs.
  */
 public final class NetworkCapabilities implements Parcelable {
     private static final String TAG = "NetworkCapabilities";
@@ -622,11 +674,19 @@
 
     /**
      * Indicates that this network should be able to prioritize latency for the internet.
+     *
+     * Starting with {@link Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, requesting this capability with
+     * {@link ConnectivityManager#requestNetwork} requires declaration in the self-certified
+     * network capabilities. See {@link NetworkRequest} for the self-certification documentation.
      */
     public static final int NET_CAPABILITY_PRIORITIZE_LATENCY = 34;
 
     /**
      * Indicates that this network should be able to prioritize bandwidth for the internet.
+     *
+     * Starting with {@link Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, requesting this capability with
+     * {@link ConnectivityManager#requestNetwork} requires declaration in the self-certified
+     * network capabilities. See {@link NetworkRequest} for the self-certification documentation.
      */
     public static final int NET_CAPABILITY_PRIORITIZE_BANDWIDTH = 35;
 
@@ -1110,6 +1170,7 @@
             TRANSPORT_LOWPAN,
             TRANSPORT_TEST,
             TRANSPORT_USB,
+            TRANSPORT_THREAD,
     })
     public @interface Transport { }
 
@@ -1161,10 +1222,15 @@
      */
     public static final int TRANSPORT_USB = 8;
 
+    /**
+     * Indicates this network uses a Thread transport.
+     */
+    public static final int TRANSPORT_THREAD = 9;
+
     /** @hide */
     public static final int MIN_TRANSPORT = TRANSPORT_CELLULAR;
     /** @hide */
-    public static final int MAX_TRANSPORT = TRANSPORT_USB;
+    public static final int MAX_TRANSPORT = TRANSPORT_THREAD;
 
     private static final int ALL_VALID_TRANSPORTS;
     static {
@@ -1189,7 +1255,8 @@
         "WIFI_AWARE",
         "LOWPAN",
         "TEST",
-        "USB"
+        "USB",
+        "THREAD",
     };
 
     /**
diff --git a/framework/src/android/net/NetworkRequest.java b/framework/src/android/net/NetworkRequest.java
index b7a6076..6c351d0 100644
--- a/framework/src/android/net/NetworkRequest.java
+++ b/framework/src/android/net/NetworkRequest.java
@@ -54,9 +54,92 @@
 import java.util.Set;
 
 /**
- * Defines a request for a network, made through {@link NetworkRequest.Builder} and used
- * to request a network via {@link ConnectivityManager#requestNetwork} or listen for changes
- * via {@link ConnectivityManager#registerNetworkCallback}.
+ * An object describing a network that the application is interested in.
+ *
+ * <p>@see <a href="https://developer.android.com/training/basics/network-ops/reading-network-state>
+ * this general guide</a> on how to use NetworkCapabilities and related classes.
+ *
+ * NetworkRequest defines a request for a network, made through
+ * {@link NetworkRequest.Builder} and used to request a network via
+ * {@link ConnectivityManager#requestNetwork} or to listen for changes
+ * via the {@link ConnectivityManager#registerNetworkCallback} family of
+ * functions.
+ *
+ * <p>{@link ConnectivityManager#requestNetwork} will try to find a connected
+ * network matching the NetworkRequest, and return it if there is one.
+ * As long as the request is outstanding, the system will try to find the best
+ * possible network that matches the request. The request will keep up the
+ * currently best connected network, and if a better one is found (e.g. cheaper
+ * or faster) the system will bring up that better network to better serve the
+ * request. A request filed with {@link ConnectivityManager#requestNetwork} will
+ * only match one network at a time (the one the system thinks is best), even if
+ * other networks can match the request that are being kept up by other requests.
+ *
+ * For example, an application needing a network with
+ * {@link NetworkCapabilities#NET_CAPABILITY_INTERNET} should use
+ * {@link ConnectivityManager#requestNetwork} to request the system keeps one up.
+ * A general cellular network can satisfy this request, but if the system finds
+ * a free Wi-Fi network which is expected to be faster, it will try and connect
+ * to that Wi-Fi network and switch the request over to it once it is connected.
+ * The cell network may stay connected if there are outstanding requests (from
+ * the same app or from other apps on the system) that match the cell network
+ * but not the Wi-Fi network, such as a request with {@link NetworkCapabilities#NET_CAPABILITY_MMS}.
+ *
+ * When a network is no longer needed to serve any request, the system can
+ * tear it down at any time and usually does so immediately, so make sure to
+ * keep up requests for the networks your app needs.
+ *
+ * <p>By contrast, requests filed with {@link ConnectivityManager#registerNetworkCallback}
+ * will receive callbacks for all matching networks, and will not cause the system to
+ * keep up the networks they match. Use this to listen to networks that the device is
+ * connected to, but that you don't want the system to keep up for your use case.
+ *
+ * <p>Applications build a NetworkRequest and pass it to one of the
+ * {@link ConnectivityManager} methods above together with a
+ * {@link ConnectivityManager.NetworkCallback} object. The callback
+ * will then start receiving method calls about networks that match
+ * the request.
+ *
+ * <p>Networks are brought up and/or matched according to the capabilities
+ * set in the builder. For example, a request with
+ * {@link NetworkCapabilities#NET_CAPABILITY_MMS} lets the system match
+ * and/or bring up a network that is capable to send MMS. A request with
+ * {@link NetworkCapabilities#NET_CAPABILITY_NOT_METERED} matches a network
+ * that doesn't charge the user for usage. See
+ * {@link NetworkCapabilities} for a list of capabilities and their
+ * description.
+ *
+ * <p>While all capabilities can be matched with the
+ * {@link ConnectivityManager#registerNetworkCallback} family of methods,
+ * not all capabilities can be used to request that the system brings
+ * up a network with {@link ConnectivityManager#requestNetwork}. For example,
+ * an application cannot use {@link ConnectivityManager#requestNetwork} to
+ * ask the system to bring up a network with
+ * {@link NetworkCapabilities#NET_CAPABILITY_CAPTIVE_PORTAL}, because the
+ * system won't know if a network has a captive portal before it connects
+ * to that network. Similarly, some capabilities may require a specific
+ * permission or privilege to be requested.
+ *
+ * Look up the specific capability and the {@link ConnectivityManager#requestNetwork}
+ * method for limitations applicable to each capability.
+ *
+ * <p>Also, starting with {@link Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, some capabilities
+ * require the application to self-certify by explicitly adding the
+ * {@link android.content.pm.PackageManager#PROPERTY_SELF_CERTIFIED_NETWORK_CAPABILITIES}
+ * property in the AndroidManifest.xml, which points to an XML resource file. In the
+ * XML resource file, the application declares what kind of network capabilities the application
+ * wants to have.
+ *
+ * Here is an example self-certification XML resource file :
+ * <pre>
+ *  {@code
+ *  <network-capabilities-declaration xmlns:android="http://schemas.android.com/apk/res/android">
+ *     <uses-network-capability android:name="NET_CAPABILITY_PRIORITIZE_LATENCY"/>
+ *     <uses-network-capability android:name="NET_CAPABILITY_PRIORITIZE_BANDWIDTH"/>
+ * </network-capabilities-declaration>
+ *  }
+ *  </pre>
+ * Look up the specific capability to learn whether its usage requires this self-certification.
  */
 public class NetworkRequest implements Parcelable {
     /**
diff --git a/framework/src/android/net/SocketKeepalive.java b/framework/src/android/net/SocketKeepalive.java
index 311126e..10daf17 100644
--- a/framework/src/android/net/SocketKeepalive.java
+++ b/framework/src/android/net/SocketKeepalive.java
@@ -21,6 +21,7 @@
 import android.annotation.IntDef;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.os.Binder;
 import android.os.ParcelFileDescriptor;
@@ -374,12 +375,14 @@
      *                    the supplied {@link Callback} will see a call to
      *                    {@link Callback#onError(int)} with {@link #ERROR_INVALID_INTERVAL}.
      * @param flags Flags to enable/disable available options on this keepalive.
-     * @param underpinnedNetwork The underpinned network of this keepalive.
+     * @param underpinnedNetwork an optional network running over mNetwork that this
+     *                           keepalive is intended to keep up, e.g. an IPSec
+     *                           tunnel running over mNetwork.
      * @hide
      */
     @SystemApi(client = PRIVILEGED_APPS)
     public final void start(@IntRange(from = MIN_INTERVAL_SEC, to = MAX_INTERVAL_SEC)
-            int intervalSec, @StartFlags int flags, @NonNull Network underpinnedNetwork) {
+            int intervalSec, @StartFlags int flags, @Nullable Network underpinnedNetwork) {
         startImpl(intervalSec, flags, underpinnedNetwork);
     }
 
diff --git a/framework/src/android/net/TcpSocketKeepalive.java b/framework/src/android/net/TcpSocketKeepalive.java
index b548f6d..696889f 100644
--- a/framework/src/android/net/TcpSocketKeepalive.java
+++ b/framework/src/android/net/TcpSocketKeepalive.java
@@ -55,6 +55,12 @@
             throw new IllegalArgumentException("Illegal flag value for "
                     + this.getClass().getSimpleName() + " : " + flags);
         }
+
+        if (underpinnedNetwork != null) {
+            throw new IllegalArgumentException("Illegal underpinned network for "
+                    + this.getClass().getSimpleName() + " : " + underpinnedNetwork);
+        }
+
         mExecutor.execute(() -> {
             try {
                 mService.startTcpKeepalive(mNetwork, mPfd, intervalSec, mCallback);
diff --git a/framework/src/android/net/connectivity/ConnectivityCompatChanges.java b/framework/src/android/net/connectivity/ConnectivityCompatChanges.java
index a166675..2cfda9e 100644
--- a/framework/src/android/net/connectivity/ConnectivityCompatChanges.java
+++ b/framework/src/android/net/connectivity/ConnectivityCompatChanges.java
@@ -62,6 +62,17 @@
     // This was a platform change ID with value 191844585L before T
     public static final long RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS_T_AND_LATER = 235355681L;
 
+    /**
+     * The self certified capabilities check should be enabled after android 13.
+     *
+     * <p> See {@link android.net.NetworkCapabilities} for more details.
+     *
+     * @hide
+     */
+    @ChangeId
+    @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
+    public static final long ENABLE_SELF_CERTIFIED_CAPABILITIES_DECLARATION = 266524688;
+
     private ConnectivityCompatChanges() {
     }
 }
diff --git a/nearby/halfsheet/Android.bp b/nearby/halfsheet/Android.bp
index 2d0d327..8011dc6 100644
--- a/nearby/halfsheet/Android.bp
+++ b/nearby/halfsheet/Android.bp
@@ -27,7 +27,7 @@
     certificate: ":com.android.nearby.halfsheetcertificate",
     libs: [
         "framework-bluetooth",
-        "framework-connectivity-t",
+        "framework-connectivity-t.impl",
         "nearby-service-string",
     ],
     static_libs: [
diff --git a/nearby/halfsheet/res/values-ky/strings.xml b/nearby/halfsheet/res/values-ky/strings.xml
index 812e0e8..b0dfe20 100644
--- a/nearby/halfsheet/res/values-ky/strings.xml
+++ b/nearby/halfsheet/res/values-ky/strings.xml
@@ -25,5 +25,5 @@
     <string name="paring_action_save" msgid="6259357442067880136">"Сактоо"</string>
     <string name="paring_action_connect" msgid="4801102939608129181">"Туташуу"</string>
     <string name="paring_action_launch" msgid="8940808384126591230">"Жөндөө"</string>
-    <string name="paring_action_settings" msgid="424875657242864302">"Жөндөөлөр"</string>
+    <string name="paring_action_settings" msgid="424875657242864302">"Параметрлер"</string>
 </resources>
diff --git a/nearby/halfsheet/src/com/android/nearby/halfsheet/HalfSheetActivity.java b/nearby/halfsheet/src/com/android/nearby/halfsheet/HalfSheetActivity.java
index 2a38b8a..07e5776 100644
--- a/nearby/halfsheet/src/com/android/nearby/halfsheet/HalfSheetActivity.java
+++ b/nearby/halfsheet/src/com/android/nearby/halfsheet/HalfSheetActivity.java
@@ -16,6 +16,8 @@
 
 package com.android.nearby.halfsheet;
 
+import static android.Manifest.permission.ACCESS_FINE_LOCATION;
+
 import static com.android.nearby.halfsheet.fragment.DevicePairingFragment.APP_LAUNCH_FRAGMENT_TYPE;
 import static com.android.server.nearby.common.bluetooth.fastpair.FastPairConstants.EXTRA_MODEL_ID;
 import static com.android.server.nearby.common.fastpair.service.UserActionHandlerBase.EXTRA_MAC_ADDRESS;
@@ -226,7 +228,8 @@
                                     EXTRA_HALF_SHEET_IS_RETROACTIVE,
                                     getIntent().getBooleanExtra(EXTRA_HALF_SHEET_IS_RETROACTIVE,
                                             false))
-                            .putExtra(EXTRA_MAC_ADDRESS, mScanFastPairStoreItem.getAddress()));
+                            .putExtra(EXTRA_MAC_ADDRESS, mScanFastPairStoreItem.getAddress()),
+                    ACCESS_FINE_LOCATION);
         }
     }
 
diff --git a/nearby/halfsheet/src/com/android/nearby/halfsheet/utils/BroadcastUtils.java b/nearby/halfsheet/src/com/android/nearby/halfsheet/utils/BroadcastUtils.java
index 467997c..2f1e90a 100644
--- a/nearby/halfsheet/src/com/android/nearby/halfsheet/utils/BroadcastUtils.java
+++ b/nearby/halfsheet/src/com/android/nearby/halfsheet/utils/BroadcastUtils.java
@@ -31,6 +31,13 @@
         context.sendBroadcast(intent);
     }
 
+    /**
+     * Helps send a broadcast with specified receiver permission.
+     */
+    public static void sendBroadcast(Context context, Intent intent, String receiverPermission) {
+        context.sendBroadcast(intent, receiverPermission);
+    }
+
     private BroadcastUtils() {
     }
 }
diff --git a/netd/BpfHandler.cpp b/netd/BpfHandler.cpp
index 7950ff7..2b773c9 100644
--- a/netd/BpfHandler.cpp
+++ b/netd/BpfHandler.cpp
@@ -93,7 +93,7 @@
     // cgroup if the program is pinned properly.
     // TODO: delete the if statement once all devices should support cgroup
     // socket filter (ie. the minimum kernel version required is 4.14).
-    if (!access(CGROUP_SOCKET_PROG_PATH, F_OK)) {
+    if (bpf::isAtLeastKernelVersion(4, 14, 0)) {
         RETURN_IF_NOT_OK(
                 attachProgramToCgroup(CGROUP_SOCKET_PROG_PATH, cg_fd, BPF_CGROUP_INET_SOCK_CREATE));
     }
diff --git a/service-t/native/libs/libnetworkstats/Android.bp b/service-t/native/libs/libnetworkstats/Android.bp
index aa1ee41..f40d388 100644
--- a/service-t/native/libs/libnetworkstats/Android.bp
+++ b/service-t/native/libs/libnetworkstats/Android.bp
@@ -26,6 +26,7 @@
     srcs: [
         "BpfNetworkStats.cpp",
         "NetworkTraceHandler.cpp",
+        "NetworkTracePoller.cpp",
     ],
     shared_libs: [
         "libbase",
@@ -62,6 +63,7 @@
     srcs: [
         "BpfNetworkStatsTest.cpp",
         "NetworkTraceHandlerTest.cpp",
+        "NetworkTracePollerTest.cpp",
     ],
     cflags: [
         "-Wall",
@@ -73,6 +75,8 @@
         "libgmock",
         "libnetworkstats",
         "libperfetto_client_experimental",
+        "libprotobuf-cpp-lite",
+        "perfetto_trace_protos",
     ],
     shared_libs: [
         "libbase",
diff --git a/service-t/native/libs/libnetworkstats/NetworkTraceHandler.cpp b/service-t/native/libs/libnetworkstats/NetworkTraceHandler.cpp
index aeadb4a..696a29a 100644
--- a/service-t/native/libs/libnetworkstats/NetworkTraceHandler.cpp
+++ b/service-t/native/libs/libnetworkstats/NetworkTraceHandler.cpp
@@ -33,11 +33,62 @@
 
 namespace android {
 namespace bpf {
+using ::android::bpf::internal::NetworkTracePoller;
+using ::perfetto::protos::pbzero::NetworkPacketBundle;
 using ::perfetto::protos::pbzero::NetworkPacketEvent;
 using ::perfetto::protos::pbzero::NetworkPacketTraceConfig;
 using ::perfetto::protos::pbzero::TracePacket;
 using ::perfetto::protos::pbzero::TrafficDirection;
 
+// Bundling takes groups of packets with similar contextual fields (generally,
+// all fields except timestamp and length) and summarises them in a single trace
+// packet. For example, rather than
+//
+//   {.timestampNs = 1, .uid = 1000, .tag = 123, .len = 72}
+//   {.timestampNs = 2, .uid = 1000, .tag = 123, .len = 100}
+//   {.timestampNs = 5, .uid = 1000, .tag = 123, .len = 456}
+//
+// The output will be something like
+//   {
+//     .timestamp = 1
+//     .ctx = {.uid = 1000, .tag = 123}
+//     .timestamp = [0, 1, 4], // delta encoded
+//     .length = [72, 100, 456], // should be zipped with timestamps
+//   }
+//
+// Most workloads have many packets from few contexts. Bundling greatly reduces
+// the amount of redundant information written, thus reducing the overall trace
+// size. Interning ids are similarly based on unique bundle contexts.
+
+// Based on boost::hash_combine
+template <typename T, typename... Rest>
+void HashCombine(std::size_t& seed, const T& val, const Rest&... rest) {
+  seed ^= std::hash<T>()(val) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
+  (HashCombine(seed, rest), ...);
+}
+
+// Details summarises the timestamp and lengths of packets in a bundle.
+struct BundleDetails {
+  std::vector<std::pair<uint64_t, uint32_t>> time_and_len;
+  uint64_t minTs = std::numeric_limits<uint64_t>::max();
+  uint64_t maxTs = std::numeric_limits<uint64_t>::min();
+  uint32_t bytes = 0;
+};
+
+#define AGG_FIELDS(x)                                              \
+  (x).ifindex, (x).uid, (x).tag, (x).sport, (x).dport, (x).egress, \
+      (x).ipProto, (x).tcpFlags
+
+std::size_t BundleHash::operator()(const BundleKey& a) const {
+  std::size_t seed = 0;
+  HashCombine(seed, AGG_FIELDS(a));
+  return seed;
+}
+
+bool BundleEq::operator()(const BundleKey& a, const BundleKey& b) const {
+  return std::tie(AGG_FIELDS(a)) == std::tie(AGG_FIELDS(b));
+}
+
 // static
 void NetworkTraceHandler::RegisterDataSource() {
   ALOGD("Registering Perfetto data source");
@@ -55,13 +106,17 @@
   NetworkTraceHandler::RegisterDataSource();
 }
 
-NetworkTraceHandler::NetworkTraceHandler()
-    : NetworkTraceHandler([this](const PacketTrace& pkt) {
-        NetworkTraceHandler::Trace(
-            [this, pkt](NetworkTraceHandler::TraceContext ctx) {
-              Fill(pkt, *ctx.NewTracePacket());
-            });
-      }) {}
+// static
+NetworkTracePoller NetworkTraceHandler::sPoller(
+    [](const std::vector<PacketTrace>& packets) {
+      // Trace calls the provided callback for each active session. The context
+      // gets a reference to the NetworkTraceHandler instance associated with
+      // the session and delegates writing. The corresponding handler will write
+      // with the setting specified in the trace config.
+      NetworkTraceHandler::Trace([&](NetworkTraceHandler::TraceContext ctx) {
+        ctx.GetDataSourceLocked()->Write(packets, ctx);
+      });
+    });
 
 void NetworkTraceHandler::OnSetup(const SetupArgs& args) {
   const std::string& raw = args.config->network_packet_trace_config_raw();
@@ -72,38 +127,115 @@
     ALOGI("poll_ms is missing or below the 100ms minimum. Increasing to 100ms");
     mPollMs = 100;
   }
+
+  mInternLimit = config.intern_limit();
+  mAggregationThreshold = config.aggregation_threshold();
+  mDropLocalPort = config.drop_local_port();
+  mDropRemotePort = config.drop_remote_port();
+  mDropTcpFlags = config.drop_tcp_flags();
 }
 
 void NetworkTraceHandler::OnStart(const StartArgs&) {
-  if (!Start()) return;
-  mTaskRunner = perfetto::Platform::GetDefaultPlatform()->CreateTaskRunner({});
-  Loop();
+  mStarted = sPoller.Start(mPollMs);
 }
 
 void NetworkTraceHandler::OnStop(const StopArgs&) {
-  Stop();
-  mTaskRunner.reset();
+  if (mStarted) sPoller.Stop();
+  mStarted = false;
 }
 
-void NetworkTraceHandler::Loop() {
-  mTaskRunner->PostDelayedTask([this]() { Loop(); }, mPollMs);
-  ConsumeAll();
+void NetworkTraceHandler::Write(const std::vector<PacketTrace>& packets,
+                                NetworkTraceHandler::TraceContext& ctx) {
+  // TODO: remove this fallback once Perfetto stable has support for bundles.
+  if (!mInternLimit && !mAggregationThreshold) {
+    for (const PacketTrace& pkt : packets) {
+      auto dst = ctx.NewTracePacket();
+      dst->set_timestamp(pkt.timestampNs);
+      auto* event = dst->set_network_packet();
+      event->set_length(pkt.length);
+      Fill(pkt, event);
+    }
+    return;
+  }
+
+  uint64_t minTs = std::numeric_limits<uint64_t>::max();
+  std::unordered_map<BundleKey, BundleDetails, BundleHash, BundleEq> bundles;
+  for (const PacketTrace& pkt : packets) {
+    BundleKey key = pkt;
+
+    // Dropping fields should remove them from the output and remove them from
+    // the aggregation key. In order to do the latter without changing the hash
+    // function, set the dropped fields to zero.
+    if (mDropTcpFlags) key.tcpFlags = 0;
+    if (mDropLocalPort) (key.egress ? key.sport : key.dport) = 0;
+    if (mDropRemotePort) (key.egress ? key.dport : key.sport) = 0;
+
+    minTs = std::min(minTs, pkt.timestampNs);
+
+    BundleDetails& bundle = bundles[key];
+    bundle.time_and_len.emplace_back(pkt.timestampNs, pkt.length);
+    bundle.minTs = std::min(bundle.minTs, pkt.timestampNs);
+    bundle.maxTs = std::max(bundle.maxTs, pkt.timestampNs);
+    bundle.bytes += pkt.length;
+  }
+
+  // If state was cleared, emit a separate packet to indicate it. This uses the
+  // overall minTs so it is sorted before any packets that follow.
+  NetworkTraceState* incr_state = ctx.GetIncrementalState();
+  if (!bundles.empty() && mInternLimit && incr_state->cleared) {
+    auto clear = ctx.NewTracePacket();
+    clear->set_sequence_flags(TracePacket::SEQ_INCREMENTAL_STATE_CLEARED);
+    clear->set_timestamp(minTs);
+    incr_state->cleared = false;
+  }
+
+  for (const auto& kv : bundles) {
+    const BundleKey& key = kv.first;
+    const BundleDetails& details = kv.second;
+
+    auto dst = ctx.NewTracePacket();
+    dst->set_sequence_flags(TracePacket::SEQ_NEEDS_INCREMENTAL_STATE);
+    dst->set_timestamp(details.minTs);
+
+    auto* event = FillWithInterning(incr_state, key, dst.get());
+
+    int count = details.time_and_len.size();
+    if (!mAggregationThreshold || count < mAggregationThreshold) {
+      protozero::PackedVarInt offsets;
+      protozero::PackedVarInt lengths;
+      for (const auto& kv : details.time_and_len) {
+        offsets.Append(kv.first - details.minTs);
+        lengths.Append(kv.second);
+      }
+
+      event->set_packet_timestamps(offsets);
+      event->set_packet_lengths(lengths);
+    } else {
+      event->set_total_duration(details.maxTs - details.minTs);
+      event->set_total_length(details.bytes);
+      event->set_total_packets(count);
+    }
+  }
 }
 
-void NetworkTraceHandler::Fill(const PacketTrace& src, TracePacket& dst) {
-  dst.set_timestamp(src.timestampNs);
-  auto* event = dst.set_network_packet();
+void NetworkTraceHandler::Fill(const PacketTrace& src,
+                               NetworkPacketEvent* event) {
   event->set_direction(src.egress ? TrafficDirection::DIR_EGRESS
                                   : TrafficDirection::DIR_INGRESS);
-  event->set_length(src.length);
   event->set_uid(src.uid);
   event->set_tag(src.tag);
 
-  event->set_local_port(src.egress ? ntohs(src.sport) : ntohs(src.dport));
-  event->set_remote_port(src.egress ? ntohs(src.dport) : ntohs(src.sport));
+  if (!mDropLocalPort) {
+    event->set_local_port(ntohs(src.egress ? src.sport : src.dport));
+  }
+  if (!mDropRemotePort) {
+    event->set_remote_port(ntohs(src.egress ? src.dport : src.sport));
+  }
+  if (!mDropTcpFlags) {
+    event->set_tcp_flags(src.tcpFlags);
+  }
 
   event->set_ip_proto(src.ipProto);
-  event->set_tcp_flags(src.tcpFlags);
 
   char ifname[IF_NAMESIZE] = {};
   if (if_indextoname(src.ifindex, ifname) == ifname) {
@@ -113,59 +245,38 @@
   }
 }
 
-bool NetworkTraceHandler::Start() {
-  ALOGD("Starting datasource");
+NetworkPacketBundle* NetworkTraceHandler::FillWithInterning(
+    NetworkTraceState* state, const BundleKey& key, TracePacket* dst) {
+  uint64_t iid = 0;
+  bool found = false;
 
-  auto status = mConfigurationMap.init(PACKET_TRACE_ENABLED_MAP_PATH);
-  if (!status.ok()) {
-    ALOGW("Failed to bind config map: %s", status.error().message().c_str());
-    return false;
+  if (state->iids.size() < mInternLimit) {
+    auto [iter, success] = state->iids.try_emplace(key, state->iids.size() + 1);
+    iid = iter->second;
+    found = true;
+
+    if (success) {
+      // If we successfully empaced, record the newly interned data.
+      auto* packet_context = dst->set_interned_data()->add_packet_context();
+      Fill(key, packet_context->set_ctx());
+      packet_context->set_iid(iid);
+    }
+  } else {
+    auto iter = state->iids.find(key);
+    if (iter != state->iids.end()) {
+      iid = iter->second;
+      found = true;
+    }
   }
 
-  auto rb = BpfRingbuf<PacketTrace>::Create(PACKET_TRACE_RINGBUF_PATH);
-  if (!rb.ok()) {
-    ALOGW("Failed to create ringbuf: %s", rb.error().message().c_str());
-    return false;
+  auto* event = dst->set_network_packet_bundle();
+  if (found) {
+    event->set_iid(iid);
+  } else {
+    Fill(key, event->set_ctx());
   }
 
-  mRingBuffer = std::move(*rb);
-
-  auto res = mConfigurationMap.writeValue(0, true, BPF_ANY);
-  if (!res.ok()) {
-    ALOGW("Failed to enable tracing: %s", res.error().message().c_str());
-    return false;
-  }
-
-  return true;
-}
-
-bool NetworkTraceHandler::Stop() {
-  ALOGD("Stopping datasource");
-
-  auto res = mConfigurationMap.writeValue(0, false, BPF_ANY);
-  if (!res.ok()) {
-    ALOGW("Failed to disable tracing: %s", res.error().message().c_str());
-    return false;
-  }
-
-  mRingBuffer.reset();
-
-  return true;
-}
-
-bool NetworkTraceHandler::ConsumeAll() {
-  if (mRingBuffer == nullptr) {
-    ALOGW("Tracing is not active");
-    return false;
-  }
-
-  base::Result<int> ret = mRingBuffer->ConsumeAll(mCallback);
-  if (!ret.ok()) {
-    ALOGW("Failed to poll ringbuf: %s", ret.error().message().c_str());
-    return false;
-  }
-
-  return true;
+  return event;
 }
 
 }  // namespace bpf
diff --git a/service-t/native/libs/libnetworkstats/NetworkTraceHandlerTest.cpp b/service-t/native/libs/libnetworkstats/NetworkTraceHandlerTest.cpp
index 560194f..c9eb183 100644
--- a/service-t/native/libs/libnetworkstats/NetworkTraceHandlerTest.cpp
+++ b/service-t/native/libs/libnetworkstats/NetworkTraceHandlerTest.cpp
@@ -14,187 +14,380 @@
  * limitations under the License.
  */
 
-#include <android-base/unique_fd.h>
-#include <android/multinetwork.h>
-#include <arpa/inet.h>
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
-#include <inttypes.h>
-#include <net/if.h>
-#include <netinet/tcp.h>
-#include <sys/socket.h>
-#include <sys/types.h>
-#include <unistd.h>
 
 #include <vector>
 
 #include "netdbpf/NetworkTraceHandler.h"
-
-using ::testing::AllOf;
-using ::testing::AnyOf;
-using ::testing::Each;
-using ::testing::Eq;
-using ::testing::Field;
-using ::testing::Test;
+#include "protos/perfetto/config/android/network_trace_config.gen.h"
+#include "protos/perfetto/trace/android/network_trace.pb.h"
+#include "protos/perfetto/trace/trace.pb.h"
+#include "protos/perfetto/trace/trace_packet.pb.h"
 
 namespace android {
 namespace bpf {
+using ::perfetto::protos::NetworkPacketEvent;
+using ::perfetto::protos::NetworkPacketTraceConfig;
+using ::perfetto::protos::Trace;
+using ::perfetto::protos::TracePacket;
+using ::perfetto::protos::TrafficDirection;
 
-__be16 bindAndListen(int s) {
-  sockaddr_in sin = {.sin_family = AF_INET};
-  socklen_t len = sizeof(sin);
-  if (bind(s, (sockaddr*)&sin, sizeof(sin))) return 0;
-  if (listen(s, 1)) return 0;
-  if (getsockname(s, (sockaddr*)&sin, &len)) return 0;
-  return sin.sin_port;
-}
-
-// This takes tcp flag constants from the standard library and makes them usable
-// with the flags we get from BPF. The standard library flags are big endian
-// whereas the BPF flags are reported in host byte order. BPF also trims the
-// flags down to the 8 single-bit flag bits (fin, syn, rst, etc).
-constexpr inline uint8_t FlagToHost(__be32 be_unix_flags) {
-  return ntohl(be_unix_flags) >> 16;
-}
-
-// Pretty prints all fields for a list of packets (useful for debugging).
-struct PacketPrinter {
-  const std::vector<PacketTrace>& data;
-  static constexpr char kTcpFlagNames[] = "FSRPAUEC";
-
-  friend std::ostream& operator<<(std::ostream& os, const PacketPrinter& d) {
-    os << "Packet count: " << d.data.size();
-    for (const PacketTrace& info : d.data) {
-      os << "\nifidx=" << info.ifindex;
-      os << ", len=" << info.length;
-      os << ", uid=" << info.uid;
-      os << ", tag=" << info.tag;
-      os << ", sport=" << info.sport;
-      os << ", dport=" << info.dport;
-      os << ", direction=" << (info.egress ? "egress" : "ingress");
-      os << ", proto=" << static_cast<int>(info.ipProto);
-      os << ", ip=" << static_cast<int>(info.ipVersion);
-      os << ", flags=";
-      for (int i = 0; i < 8; i++) {
-        os << ((info.tcpFlags & (1 << i)) ? kTcpFlagNames[i] : '.');
-      }
-    }
-    return os;
-  }
+// This handler makes OnStart and OnStop a no-op so that tracing is not really
+// started on the device.
+class HandlerForTest : public NetworkTraceHandler {
+ public:
+  void OnStart(const StartArgs&) override {}
+  void OnStop(const StopArgs&) override {}
 };
 
 class NetworkTraceHandlerTest : public testing::Test {
  protected:
-  void SetUp() {
-    if (access(PACKET_TRACE_RINGBUF_PATH, R_OK)) {
-      GTEST_SKIP() << "Network tracing is not enabled/loaded on this build.";
+  // Starts a tracing session with the handler under test.
+  std::unique_ptr<perfetto::TracingSession> StartTracing(
+      NetworkPacketTraceConfig settings) {
+    perfetto::TracingInitArgs args;
+    args.backends = perfetto::kInProcessBackend;
+    perfetto::Tracing::Initialize(args);
+
+    perfetto::DataSourceDescriptor dsd;
+    dsd.set_name("test.network_packets");
+    HandlerForTest::Register(dsd);
+
+    perfetto::TraceConfig cfg;
+    cfg.add_buffers()->set_size_kb(1024);
+    auto* config = cfg.add_data_sources()->mutable_config();
+    config->set_name("test.network_packets");
+    config->set_network_packet_trace_config_raw(settings.SerializeAsString());
+
+    auto session = perfetto::Tracing::NewTrace(perfetto::kInProcessBackend);
+    session->Setup(cfg);
+    session->StartBlocking();
+    return session;
+  }
+
+  // Stops the trace session and reports all relevant trace packets.
+  bool StopTracing(perfetto::TracingSession* session,
+                   std::vector<TracePacket>* output) {
+    session->StopBlocking();
+
+    Trace trace;
+    std::vector<char> raw_trace = session->ReadTraceBlocking();
+    if (!trace.ParseFromArray(raw_trace.data(), raw_trace.size())) {
+      ADD_FAILURE() << "trace.ParseFromArray failed";
+      return false;
     }
-    if (sizeof(void*) != 8) {
-      GTEST_SKIP() << "Network tracing requires 64-bit build.";
+
+    // This is a real trace and includes irrelevant trace packets such as trace
+    // metadata. The following strips the results to just the packets we want.
+    for (const auto& pkt : trace.packet()) {
+      if (pkt.has_network_packet() || pkt.has_network_packet_bundle()) {
+        output->emplace_back(pkt);
+      }
     }
+
+    return true;
+  }
+
+  // This runs a trace with a single call to Write.
+  bool TraceAndSortPackets(const std::vector<PacketTrace>& input,
+                           std::vector<TracePacket>* output,
+                           NetworkPacketTraceConfig config = {}) {
+    auto session = StartTracing(config);
+    HandlerForTest::Trace([&](HandlerForTest::TraceContext ctx) {
+      ctx.GetDataSourceLocked()->Write(input, ctx);
+      ctx.Flush();
+    });
+
+    if (!StopTracing(session.get(), output)) {
+      return false;
+    }
+
+    // Sort to provide deterministic ordering regardless of Perfetto internals
+    // or implementation-defined (e.g. hash map) reshuffling.
+    std::sort(output->begin(), output->end(),
+              [](const TracePacket& a, const TracePacket& b) {
+                return a.timestamp() < b.timestamp();
+              });
+
+    return true;
   }
 };
 
-TEST_F(NetworkTraceHandlerTest, PollWhileInactive) {
-  NetworkTraceHandler handler([&](const PacketTrace& pkt) {});
+TEST_F(NetworkTraceHandlerTest, WriteBasicFields) {
+  std::vector<PacketTrace> input = {
+      PacketTrace{
+          .timestampNs = 1000,
+          .length = 100,
+          .uid = 10,
+          .tag = 123,
+          .ipProto = 6,
+          .tcpFlags = 1,
+      },
+  };
 
-  // One succeed after start and before stop.
-  EXPECT_FALSE(handler.ConsumeAll());
-  ASSERT_TRUE(handler.Start());
-  EXPECT_TRUE(handler.ConsumeAll());
-  ASSERT_TRUE(handler.Stop());
-  EXPECT_FALSE(handler.ConsumeAll());
+  std::vector<TracePacket> events;
+  ASSERT_TRUE(TraceAndSortPackets(input, &events));
+
+  ASSERT_EQ(events.size(), 1);
+  EXPECT_THAT(events[0].timestamp(), 1000);
+  EXPECT_THAT(events[0].network_packet().uid(), 10);
+  EXPECT_THAT(events[0].network_packet().tag(), 123);
+  EXPECT_THAT(events[0].network_packet().ip_proto(), 6);
+  EXPECT_THAT(events[0].network_packet().tcp_flags(), 1);
+  EXPECT_THAT(events[0].network_packet().length(), 100);
 }
 
-TEST_F(NetworkTraceHandlerTest, TraceTcpSession) {
-  __be16 server_port = 0;
-  std::vector<PacketTrace> packets;
+TEST_F(NetworkTraceHandlerTest, WriteDirectionAndPorts) {
+  std::vector<PacketTrace> input = {
+      PacketTrace{
+          .timestampNs = 1,
+          .sport = htons(8080),
+          .dport = htons(443),
+          .egress = true,
+      },
+      PacketTrace{
+          .timestampNs = 2,
+          .sport = htons(443),
+          .dport = htons(8080),
+          .egress = false,
+      },
+  };
 
-  // Record all packets with the bound address and current uid. This callback is
-  // involked only within ConsumeAll, at which point the port should have
-  // already been filled in and all packets have been processed.
-  NetworkTraceHandler handler([&](const PacketTrace& pkt) {
-    if (pkt.sport != server_port && pkt.dport != server_port) return;
-    if (pkt.uid != getuid()) return;
-    packets.push_back(pkt);
+  std::vector<TracePacket> events;
+  ASSERT_TRUE(TraceAndSortPackets(input, &events));
+
+  ASSERT_EQ(events.size(), 2);
+  EXPECT_THAT(events[0].network_packet().local_port(), 8080);
+  EXPECT_THAT(events[0].network_packet().remote_port(), 443);
+  EXPECT_THAT(events[0].network_packet().direction(),
+              TrafficDirection::DIR_EGRESS);
+  EXPECT_THAT(events[1].network_packet().local_port(), 8080);
+  EXPECT_THAT(events[1].network_packet().remote_port(), 443);
+  EXPECT_THAT(events[1].network_packet().direction(),
+              TrafficDirection::DIR_INGRESS);
+}
+
+TEST_F(NetworkTraceHandlerTest, BasicBundling) {
+  // TODO: remove this once bundling becomes default. Until then, set arbitrary
+  // aggregation threshold to enable bundling.
+  NetworkPacketTraceConfig config;
+  config.set_aggregation_threshold(10);
+
+  std::vector<PacketTrace> input = {
+      PacketTrace{.uid = 123, .timestampNs = 2, .length = 200},
+      PacketTrace{.uid = 123, .timestampNs = 1, .length = 100},
+      PacketTrace{.uid = 123, .timestampNs = 4, .length = 300},
+
+      PacketTrace{.uid = 456, .timestampNs = 2, .length = 400},
+      PacketTrace{.uid = 456, .timestampNs = 4, .length = 100},
+  };
+
+  std::vector<TracePacket> events;
+  ASSERT_TRUE(TraceAndSortPackets(input, &events, config));
+
+  ASSERT_EQ(events.size(), 2);
+
+  EXPECT_THAT(events[0].timestamp(), 1);
+  EXPECT_THAT(events[0].network_packet_bundle().ctx().uid(), 123);
+  EXPECT_THAT(events[0].network_packet_bundle().packet_lengths(),
+              testing::ElementsAre(200, 100, 300));
+  EXPECT_THAT(events[0].network_packet_bundle().packet_timestamps(),
+              testing::ElementsAre(1, 0, 3));
+
+  EXPECT_THAT(events[1].timestamp(), 2);
+  EXPECT_THAT(events[1].network_packet_bundle().ctx().uid(), 456);
+  EXPECT_THAT(events[1].network_packet_bundle().packet_lengths(),
+              testing::ElementsAre(400, 100));
+  EXPECT_THAT(events[1].network_packet_bundle().packet_timestamps(),
+              testing::ElementsAre(0, 2));
+}
+
+TEST_F(NetworkTraceHandlerTest, AggregationThreshold) {
+  // With an aggregation threshold of 3, the set of packets with uid=123 will
+  // be aggregated (3>=3) whereas packets with uid=456 get per-packet info.
+  NetworkPacketTraceConfig config;
+  config.set_aggregation_threshold(3);
+
+  std::vector<PacketTrace> input = {
+      PacketTrace{.uid = 123, .timestampNs = 2, .length = 200},
+      PacketTrace{.uid = 123, .timestampNs = 1, .length = 100},
+      PacketTrace{.uid = 123, .timestampNs = 4, .length = 300},
+
+      PacketTrace{.uid = 456, .timestampNs = 2, .length = 400},
+      PacketTrace{.uid = 456, .timestampNs = 4, .length = 100},
+  };
+
+  std::vector<TracePacket> events;
+  ASSERT_TRUE(TraceAndSortPackets(input, &events, config));
+
+  ASSERT_EQ(events.size(), 2);
+
+  EXPECT_EQ(events[0].timestamp(), 1);
+  EXPECT_EQ(events[0].network_packet_bundle().ctx().uid(), 123);
+  EXPECT_EQ(events[0].network_packet_bundle().total_duration(), 3);
+  EXPECT_EQ(events[0].network_packet_bundle().total_packets(), 3);
+  EXPECT_EQ(events[0].network_packet_bundle().total_length(), 600);
+
+  EXPECT_EQ(events[1].timestamp(), 2);
+  EXPECT_EQ(events[1].network_packet_bundle().ctx().uid(), 456);
+  EXPECT_THAT(events[1].network_packet_bundle().packet_lengths(),
+              testing::ElementsAre(400, 100));
+  EXPECT_THAT(events[1].network_packet_bundle().packet_timestamps(),
+              testing::ElementsAre(0, 2));
+}
+
+TEST_F(NetworkTraceHandlerTest, DropLocalPort) {
+  NetworkPacketTraceConfig config;
+  config.set_drop_local_port(true);
+  config.set_aggregation_threshold(10);
+
+  __be16 a = htons(10000);
+  __be16 b = htons(10001);
+  std::vector<PacketTrace> input = {
+      // Recall that local is `src` for egress and `dst` for ingress.
+      PacketTrace{.timestampNs = 1, .length = 2, .egress = true, .sport = a},
+      PacketTrace{.timestampNs = 2, .length = 4, .egress = false, .dport = a},
+      PacketTrace{.timestampNs = 3, .length = 6, .egress = true, .sport = b},
+      PacketTrace{.timestampNs = 4, .length = 8, .egress = false, .dport = b},
+  };
+
+  std::vector<TracePacket> events;
+  ASSERT_TRUE(TraceAndSortPackets(input, &events, config));
+  ASSERT_EQ(events.size(), 2);
+
+  // Despite having different local ports, drop and bundle by remaining fields.
+  EXPECT_EQ(events[0].network_packet_bundle().ctx().direction(),
+            TrafficDirection::DIR_EGRESS);
+  EXPECT_THAT(events[0].network_packet_bundle().packet_lengths(),
+              testing::ElementsAre(2, 6));
+
+  EXPECT_EQ(events[1].network_packet_bundle().ctx().direction(),
+            TrafficDirection::DIR_INGRESS);
+  EXPECT_THAT(events[1].network_packet_bundle().packet_lengths(),
+              testing::ElementsAre(4, 8));
+
+  // Local port shouldn't be in output.
+  EXPECT_FALSE(events[0].network_packet_bundle().ctx().has_local_port());
+  EXPECT_FALSE(events[1].network_packet_bundle().ctx().has_local_port());
+}
+
+TEST_F(NetworkTraceHandlerTest, DropRemotePort) {
+  NetworkPacketTraceConfig config;
+  config.set_drop_remote_port(true);
+  config.set_aggregation_threshold(10);
+
+  __be16 a = htons(443);
+  __be16 b = htons(80);
+  std::vector<PacketTrace> input = {
+      // Recall that remote is `dst` for egress and `src` for ingress.
+      PacketTrace{.timestampNs = 1, .length = 2, .egress = true, .dport = a},
+      PacketTrace{.timestampNs = 2, .length = 4, .egress = false, .sport = a},
+      PacketTrace{.timestampNs = 3, .length = 6, .egress = true, .dport = b},
+      PacketTrace{.timestampNs = 4, .length = 8, .egress = false, .sport = b},
+  };
+
+  std::vector<TracePacket> events;
+  ASSERT_TRUE(TraceAndSortPackets(input, &events, config));
+  ASSERT_EQ(events.size(), 2);
+
+  // Despite having different remote ports, drop and bundle by remaining fields.
+  EXPECT_EQ(events[0].network_packet_bundle().ctx().direction(),
+            TrafficDirection::DIR_EGRESS);
+  EXPECT_THAT(events[0].network_packet_bundle().packet_lengths(),
+              testing::ElementsAre(2, 6));
+
+  EXPECT_EQ(events[1].network_packet_bundle().ctx().direction(),
+            TrafficDirection::DIR_INGRESS);
+  EXPECT_THAT(events[1].network_packet_bundle().packet_lengths(),
+              testing::ElementsAre(4, 8));
+
+  // Remote port shouldn't be in output.
+  EXPECT_FALSE(events[0].network_packet_bundle().ctx().has_remote_port());
+  EXPECT_FALSE(events[1].network_packet_bundle().ctx().has_remote_port());
+}
+
+TEST_F(NetworkTraceHandlerTest, DropTcpFlags) {
+  NetworkPacketTraceConfig config;
+  config.set_drop_tcp_flags(true);
+  config.set_aggregation_threshold(10);
+
+  std::vector<PacketTrace> input = {
+      PacketTrace{.timestampNs = 1, .uid = 123, .length = 1, .tcpFlags = 1},
+      PacketTrace{.timestampNs = 2, .uid = 123, .length = 2, .tcpFlags = 2},
+      PacketTrace{.timestampNs = 3, .uid = 456, .length = 3, .tcpFlags = 1},
+      PacketTrace{.timestampNs = 4, .uid = 456, .length = 4, .tcpFlags = 2},
+  };
+
+  std::vector<TracePacket> events;
+  ASSERT_TRUE(TraceAndSortPackets(input, &events, config));
+
+  ASSERT_EQ(events.size(), 2);
+
+  // Despite having different tcp flags, drop and bundle by remaining fields.
+  EXPECT_EQ(events[0].network_packet_bundle().ctx().uid(), 123);
+  EXPECT_THAT(events[0].network_packet_bundle().packet_lengths(),
+              testing::ElementsAre(1, 2));
+
+  EXPECT_EQ(events[1].network_packet_bundle().ctx().uid(), 456);
+  EXPECT_THAT(events[1].network_packet_bundle().packet_lengths(),
+              testing::ElementsAre(3, 4));
+
+  // Tcp flags shouldn't be in output.
+  EXPECT_FALSE(events[0].network_packet_bundle().ctx().has_tcp_flags());
+  EXPECT_FALSE(events[1].network_packet_bundle().ctx().has_tcp_flags());
+}
+
+TEST_F(NetworkTraceHandlerTest, Interning) {
+  NetworkPacketTraceConfig config;
+  config.set_intern_limit(2);
+
+  // The test writes 4 packets coming from three sources (uids). With an intern
+  // limit of 2, the first two sources should be interned. This test splits this
+  // into individual writes since internally an unordered map is used and would
+  // otherwise non-deterministically choose what to intern (this is fine for
+  // real use, but not good for test assertions).
+  std::vector<std::vector<PacketTrace>> inputs = {
+      {PacketTrace{.timestampNs = 1, .uid = 123}},
+      {PacketTrace{.timestampNs = 2, .uid = 456}},
+      {PacketTrace{.timestampNs = 3, .uid = 789}},
+      {PacketTrace{.timestampNs = 4, .uid = 123}},
+  };
+
+  auto session = StartTracing(config);
+
+  HandlerForTest::Trace([&](HandlerForTest::TraceContext ctx) {
+    ctx.GetDataSourceLocked()->Write(inputs[0], ctx);
+    ctx.GetDataSourceLocked()->Write(inputs[1], ctx);
+    ctx.GetDataSourceLocked()->Write(inputs[2], ctx);
+    ctx.GetDataSourceLocked()->Write(inputs[3], ctx);
+    ctx.Flush();
   });
 
-  ASSERT_TRUE(handler.Start());
-  const uint32_t kClientTag = 2468;
-  const uint32_t kServerTag = 1357;
+  std::vector<TracePacket> events;
+  ASSERT_TRUE(StopTracing(session.get(), &events));
 
-  // Go through a typical connection sequence between two v4 sockets using tcp.
-  // This covers connection handshake, shutdown, and one data packet.
-  {
-    android::base::unique_fd clientsocket(socket(AF_INET, SOCK_STREAM, 0));
-    ASSERT_NE(-1, clientsocket) << "Failed to open client socket";
-    ASSERT_EQ(android_tag_socket(clientsocket, kClientTag), 0);
+  ASSERT_EQ(events.size(), 4);
 
-    android::base::unique_fd serversocket(socket(AF_INET, SOCK_STREAM, 0));
-    ASSERT_NE(-1, serversocket) << "Failed to open server socket";
-    ASSERT_EQ(android_tag_socket(serversocket, kServerTag), 0);
+  // First time seen, emit new interned data, bundle uses iid instead of ctx.
+  EXPECT_EQ(events[0].network_packet_bundle().iid(), 1);
+  ASSERT_EQ(events[0].interned_data().packet_context().size(), 1);
+  EXPECT_EQ(events[0].interned_data().packet_context(0).iid(), 1);
+  EXPECT_EQ(events[0].interned_data().packet_context(0).ctx().uid(), 123);
 
-    server_port = bindAndListen(serversocket);
-    ASSERT_NE(0, server_port) << "Can't bind to server port";
+  // First time seen, emit new interned data, bundle uses iid instead of ctx.
+  EXPECT_EQ(events[1].network_packet_bundle().iid(), 2);
+  ASSERT_EQ(events[1].interned_data().packet_context().size(), 1);
+  EXPECT_EQ(events[1].interned_data().packet_context(0).iid(), 2);
+  EXPECT_EQ(events[1].interned_data().packet_context(0).ctx().uid(), 456);
 
-    sockaddr_in addr = {.sin_family = AF_INET, .sin_port = server_port};
-    ASSERT_EQ(0, connect(clientsocket, (sockaddr*)&addr, sizeof(addr)))
-        << "connect to loopback failed: " << strerror(errno);
+  // Not enough room in intern table (limit 2), inline the context.
+  EXPECT_EQ(events[2].network_packet_bundle().ctx().uid(), 789);
+  EXPECT_EQ(events[2].interned_data().packet_context().size(), 0);
 
-    int accepted = accept(serversocket, nullptr, nullptr);
-    ASSERT_NE(-1, accepted) << "accept connection failed: " << strerror(errno);
-
-    const char data[] = "abcdefghijklmnopqrstuvwxyz";
-    EXPECT_EQ(send(clientsocket, data, sizeof(data), 0), sizeof(data))
-        << "failed to send message: " << strerror(errno);
-
-    char buff[100] = {};
-    EXPECT_EQ(recv(accepted, buff, sizeof(buff), 0), sizeof(data))
-        << "failed to receive message: " << strerror(errno);
-
-    EXPECT_EQ(std::string(data), std::string(buff));
-  }
-
-  ASSERT_TRUE(handler.ConsumeAll());
-  ASSERT_TRUE(handler.Stop());
-
-  // There are 12 packets in total (6 messages: each seen by client & server):
-  // 1. Client connects to server with syn
-  // 2. Server responds with syn ack
-  // 3. Client responds with ack
-  // 4. Client sends data with psh ack
-  // 5. Server acks the data packet
-  // 6. Client closes connection with fin ack
-  ASSERT_EQ(packets.size(), 12) << PacketPrinter{packets};
-
-  // All packets should be TCP packets.
-  EXPECT_THAT(packets, Each(Field(&PacketTrace::ipProto, Eq(IPPROTO_TCP))));
-
-  // Packet 1: client requests connection with server.
-  EXPECT_EQ(packets[0].egress, 1) << PacketPrinter{packets};
-  EXPECT_EQ(packets[0].dport, server_port) << PacketPrinter{packets};
-  EXPECT_EQ(packets[0].tag, kClientTag) << PacketPrinter{packets};
-  EXPECT_EQ(packets[0].tcpFlags, FlagToHost(TCP_FLAG_SYN))
-      << PacketPrinter{packets};
-
-  // Packet 2: server receives request from client.
-  EXPECT_EQ(packets[1].egress, 0) << PacketPrinter{packets};
-  EXPECT_EQ(packets[1].dport, server_port) << PacketPrinter{packets};
-  EXPECT_EQ(packets[1].tag, kServerTag) << PacketPrinter{packets};
-  EXPECT_EQ(packets[1].tcpFlags, FlagToHost(TCP_FLAG_SYN))
-      << PacketPrinter{packets};
-
-  // Packet 3: server replies back with syn ack.
-  EXPECT_EQ(packets[2].egress, 1) << PacketPrinter{packets};
-  EXPECT_EQ(packets[2].sport, server_port) << PacketPrinter{packets};
-  EXPECT_EQ(packets[2].tcpFlags, FlagToHost(TCP_FLAG_SYN | TCP_FLAG_ACK))
-      << PacketPrinter{packets};
-
-  // Packet 4: client receives the server's syn ack.
-  EXPECT_EQ(packets[3].egress, 0) << PacketPrinter{packets};
-  EXPECT_EQ(packets[3].sport, server_port) << PacketPrinter{packets};
-  EXPECT_EQ(packets[3].tcpFlags, FlagToHost(TCP_FLAG_SYN | TCP_FLAG_ACK))
-      << PacketPrinter{packets};
+  // Second time seen, no need to re-emit interned data, only record iid.
+  EXPECT_EQ(events[3].network_packet_bundle().iid(), 1);
+  EXPECT_EQ(events[3].interned_data().packet_context().size(), 0);
 }
 
 }  // namespace bpf
diff --git a/service-t/native/libs/libnetworkstats/NetworkTracePoller.cpp b/service-t/native/libs/libnetworkstats/NetworkTracePoller.cpp
new file mode 100644
index 0000000..3abb49a
--- /dev/null
+++ b/service-t/native/libs/libnetworkstats/NetworkTracePoller.cpp
@@ -0,0 +1,134 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "NetworkTrace"
+
+#include "netdbpf/NetworkTracePoller.h"
+
+#include <bpf/BpfUtils.h>
+#include <log/log.h>
+#include <perfetto/tracing/platform.h>
+#include <perfetto/tracing/tracing.h>
+
+namespace android {
+namespace bpf {
+namespace internal {
+
+void NetworkTracePoller::SchedulePolling() {
+  // Schedules another run of ourselves to recursively poll periodically.
+  mTaskRunner->PostDelayedTask(
+      [this]() {
+        mMutex.lock();
+        SchedulePolling();
+        ConsumeAllLocked();
+        mMutex.unlock();
+      },
+      mPollMs);
+}
+
+bool NetworkTracePoller::Start(uint32_t pollMs) {
+  ALOGD("Starting datasource");
+
+  std::scoped_lock<std::mutex> lock(mMutex);
+  if (mSessionCount > 0) {
+    if (mPollMs != pollMs) {
+      // Nothing technical prevents mPollMs from changing, it's just unclear
+      // what the right behavior is. Taking the min of active values could poll
+      // too frequently giving some sessions too much data. Taking the max could
+      // be too infrequent. For now, do nothing.
+      ALOGI("poll_ms can't be changed while running, ignoring poll_ms=%d",
+            pollMs);
+    }
+    mSessionCount++;
+    return true;
+  }
+
+  auto status = mConfigurationMap.init(PACKET_TRACE_ENABLED_MAP_PATH);
+  if (!status.ok()) {
+    ALOGW("Failed to bind config map: %s", status.error().message().c_str());
+    return false;
+  }
+
+  auto rb = BpfRingbuf<PacketTrace>::Create(PACKET_TRACE_RINGBUF_PATH);
+  if (!rb.ok()) {
+    ALOGW("Failed to create ringbuf: %s", rb.error().message().c_str());
+    return false;
+  }
+
+  mRingBuffer = std::move(*rb);
+
+  auto res = mConfigurationMap.writeValue(0, true, BPF_ANY);
+  if (!res.ok()) {
+    ALOGW("Failed to enable tracing: %s", res.error().message().c_str());
+    return false;
+  }
+
+  // Start a task runner to run ConsumeAll every mPollMs milliseconds.
+  mTaskRunner = perfetto::Platform::GetDefaultPlatform()->CreateTaskRunner({});
+  mPollMs = pollMs;
+  SchedulePolling();
+
+  mSessionCount++;
+  return true;
+}
+
+bool NetworkTracePoller::Stop() {
+  ALOGD("Stopping datasource");
+
+  std::scoped_lock<std::mutex> lock(mMutex);
+  if (mSessionCount == 0) return false;  // This should never happen
+
+  // If this isn't the last session, don't clean up yet.
+  if (--mSessionCount > 0) return true;
+
+  auto res = mConfigurationMap.writeValue(0, false, BPF_ANY);
+  if (!res.ok()) {
+    ALOGW("Failed to disable tracing: %s", res.error().message().c_str());
+  }
+
+  mTaskRunner.reset();
+  mRingBuffer.reset();
+
+  return res.ok();
+}
+
+bool NetworkTracePoller::ConsumeAll() {
+  std::scoped_lock<std::mutex> lock(mMutex);
+  return ConsumeAllLocked();
+}
+
+bool NetworkTracePoller::ConsumeAllLocked() {
+  if (mRingBuffer == nullptr) {
+    ALOGW("Tracing is not active");
+    return false;
+  }
+
+  std::vector<PacketTrace> packets;
+  base::Result<int> ret = mRingBuffer->ConsumeAll(
+      [&](const PacketTrace& pkt) { packets.push_back(pkt); });
+  if (!ret.ok()) {
+    ALOGW("Failed to poll ringbuf: %s", ret.error().message().c_str());
+    return false;
+  }
+
+  mCallback(packets);
+
+  return true;
+}
+
+}  // namespace internal
+}  // namespace bpf
+}  // namespace android
diff --git a/service-t/native/libs/libnetworkstats/NetworkTracePollerTest.cpp b/service-t/native/libs/libnetworkstats/NetworkTracePollerTest.cpp
new file mode 100644
index 0000000..725cec1
--- /dev/null
+++ b/service-t/native/libs/libnetworkstats/NetworkTracePollerTest.cpp
@@ -0,0 +1,226 @@
+/*
+ * 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.
+ */
+
+#include <android-base/unique_fd.h>
+#include <android/multinetwork.h>
+#include <arpa/inet.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <inttypes.h>
+#include <net/if.h>
+#include <netinet/tcp.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <vector>
+
+#include "netdbpf/NetworkTracePoller.h"
+
+using ::testing::AllOf;
+using ::testing::AnyOf;
+using ::testing::Each;
+using ::testing::Eq;
+using ::testing::Field;
+using ::testing::Test;
+
+namespace android {
+namespace bpf {
+namespace internal {
+// Use uint32 max to cause the handler to never Loop. Instead, the tests will
+// manually drive things by calling ConsumeAll explicitly.
+constexpr uint32_t kNeverPoll = std::numeric_limits<uint32_t>::max();
+
+__be16 bindAndListen(int s) {
+  sockaddr_in sin = {.sin_family = AF_INET};
+  socklen_t len = sizeof(sin);
+  if (bind(s, (sockaddr*)&sin, sizeof(sin))) return 0;
+  if (listen(s, 1)) return 0;
+  if (getsockname(s, (sockaddr*)&sin, &len)) return 0;
+  return sin.sin_port;
+}
+
+// This takes tcp flag constants from the standard library and makes them usable
+// with the flags we get from BPF. The standard library flags are big endian
+// whereas the BPF flags are reported in host byte order. BPF also trims the
+// flags down to the 8 single-bit flag bits (fin, syn, rst, etc).
+constexpr inline uint8_t FlagToHost(__be32 be_unix_flags) {
+  return ntohl(be_unix_flags) >> 16;
+}
+
+// Pretty prints all fields for a list of packets (useful for debugging).
+struct PacketPrinter {
+  const std::vector<PacketTrace>& data;
+  static constexpr char kTcpFlagNames[] = "FSRPAUEC";
+
+  friend std::ostream& operator<<(std::ostream& os, const PacketPrinter& d) {
+    os << "Packet count: " << d.data.size();
+    for (const PacketTrace& info : d.data) {
+      os << "\nifidx=" << info.ifindex;
+      os << ", len=" << info.length;
+      os << ", uid=" << info.uid;
+      os << ", tag=" << info.tag;
+      os << ", sport=" << info.sport;
+      os << ", dport=" << info.dport;
+      os << ", direction=" << (info.egress ? "egress" : "ingress");
+      os << ", proto=" << static_cast<int>(info.ipProto);
+      os << ", ip=" << static_cast<int>(info.ipVersion);
+      os << ", flags=";
+      for (int i = 0; i < 8; i++) {
+        os << ((info.tcpFlags & (1 << i)) ? kTcpFlagNames[i] : '.');
+      }
+    }
+    return os;
+  }
+};
+
+class NetworkTracePollerTest : public testing::Test {
+ protected:
+  void SetUp() {
+    if (access(PACKET_TRACE_RINGBUF_PATH, R_OK)) {
+      GTEST_SKIP() << "Network tracing is not enabled/loaded on this build.";
+    }
+    if (sizeof(void*) != 8) {
+      GTEST_SKIP() << "Network tracing requires 64-bit build.";
+    }
+  }
+};
+
+TEST_F(NetworkTracePollerTest, PollWhileInactive) {
+  NetworkTracePoller handler([&](const std::vector<PacketTrace>& pkt) {});
+
+  // One succeed after start and before stop.
+  EXPECT_FALSE(handler.ConsumeAll());
+  ASSERT_TRUE(handler.Start(kNeverPoll));
+  EXPECT_TRUE(handler.ConsumeAll());
+  ASSERT_TRUE(handler.Stop());
+  EXPECT_FALSE(handler.ConsumeAll());
+}
+
+TEST_F(NetworkTracePollerTest, ConcurrentSessions) {
+  // Simulate two concurrent sessions (two starts followed by two stops). Check
+  // that tracing is stopped only after both sessions finish.
+  NetworkTracePoller handler([&](const std::vector<PacketTrace>& pkt) {});
+
+  ASSERT_TRUE(handler.Start(kNeverPoll));
+  EXPECT_TRUE(handler.ConsumeAll());
+
+  ASSERT_TRUE(handler.Start(kNeverPoll));
+  EXPECT_TRUE(handler.ConsumeAll());
+
+  ASSERT_TRUE(handler.Stop());
+  EXPECT_TRUE(handler.ConsumeAll());
+
+  ASSERT_TRUE(handler.Stop());
+  EXPECT_FALSE(handler.ConsumeAll());
+}
+
+TEST_F(NetworkTracePollerTest, TraceTcpSession) {
+  __be16 server_port = 0;
+  std::vector<PacketTrace> packets;
+
+  // Record all packets with the bound address and current uid. This callback is
+  // involked only within ConsumeAll, at which point the port should have
+  // already been filled in and all packets have been processed.
+  NetworkTracePoller handler([&](const std::vector<PacketTrace>& pkts) {
+    for (const PacketTrace& pkt : pkts) {
+      if (pkt.sport != server_port && pkt.dport != server_port) return;
+      if (pkt.uid != getuid()) return;
+      packets.push_back(pkt);
+    }
+  });
+
+  ASSERT_TRUE(handler.Start(kNeverPoll));
+  const uint32_t kClientTag = 2468;
+  const uint32_t kServerTag = 1357;
+
+  // Go through a typical connection sequence between two v4 sockets using tcp.
+  // This covers connection handshake, shutdown, and one data packet.
+  {
+    android::base::unique_fd clientsocket(socket(AF_INET, SOCK_STREAM, 0));
+    ASSERT_NE(-1, clientsocket) << "Failed to open client socket";
+    ASSERT_EQ(android_tag_socket(clientsocket, kClientTag), 0);
+
+    android::base::unique_fd serversocket(socket(AF_INET, SOCK_STREAM, 0));
+    ASSERT_NE(-1, serversocket) << "Failed to open server socket";
+    ASSERT_EQ(android_tag_socket(serversocket, kServerTag), 0);
+
+    server_port = bindAndListen(serversocket);
+    ASSERT_NE(0, server_port) << "Can't bind to server port";
+
+    sockaddr_in addr = {.sin_family = AF_INET, .sin_port = server_port};
+    ASSERT_EQ(0, connect(clientsocket, (sockaddr*)&addr, sizeof(addr)))
+        << "connect to loopback failed: " << strerror(errno);
+
+    int accepted = accept(serversocket, nullptr, nullptr);
+    ASSERT_NE(-1, accepted) << "accept connection failed: " << strerror(errno);
+
+    const char data[] = "abcdefghijklmnopqrstuvwxyz";
+    EXPECT_EQ(send(clientsocket, data, sizeof(data), 0), sizeof(data))
+        << "failed to send message: " << strerror(errno);
+
+    char buff[100] = {};
+    EXPECT_EQ(recv(accepted, buff, sizeof(buff), 0), sizeof(data))
+        << "failed to receive message: " << strerror(errno);
+
+    EXPECT_EQ(std::string(data), std::string(buff));
+  }
+
+  ASSERT_TRUE(handler.ConsumeAll());
+  ASSERT_TRUE(handler.Stop());
+
+  // There are 12 packets in total (6 messages: each seen by client & server):
+  // 1. Client connects to server with syn
+  // 2. Server responds with syn ack
+  // 3. Client responds with ack
+  // 4. Client sends data with psh ack
+  // 5. Server acks the data packet
+  // 6. Client closes connection with fin ack
+  ASSERT_EQ(packets.size(), 12) << PacketPrinter{packets};
+
+  // All packets should be TCP packets.
+  EXPECT_THAT(packets, Each(Field(&PacketTrace::ipProto, Eq(IPPROTO_TCP))));
+
+  // Packet 1: client requests connection with server.
+  EXPECT_EQ(packets[0].egress, 1) << PacketPrinter{packets};
+  EXPECT_EQ(packets[0].dport, server_port) << PacketPrinter{packets};
+  EXPECT_EQ(packets[0].tag, kClientTag) << PacketPrinter{packets};
+  EXPECT_EQ(packets[0].tcpFlags, FlagToHost(TCP_FLAG_SYN))
+      << PacketPrinter{packets};
+
+  // Packet 2: server receives request from client.
+  EXPECT_EQ(packets[1].egress, 0) << PacketPrinter{packets};
+  EXPECT_EQ(packets[1].dport, server_port) << PacketPrinter{packets};
+  EXPECT_EQ(packets[1].tag, kServerTag) << PacketPrinter{packets};
+  EXPECT_EQ(packets[1].tcpFlags, FlagToHost(TCP_FLAG_SYN))
+      << PacketPrinter{packets};
+
+  // Packet 3: server replies back with syn ack.
+  EXPECT_EQ(packets[2].egress, 1) << PacketPrinter{packets};
+  EXPECT_EQ(packets[2].sport, server_port) << PacketPrinter{packets};
+  EXPECT_EQ(packets[2].tcpFlags, FlagToHost(TCP_FLAG_SYN | TCP_FLAG_ACK))
+      << PacketPrinter{packets};
+
+  // Packet 4: client receives the server's syn ack.
+  EXPECT_EQ(packets[3].egress, 0) << PacketPrinter{packets};
+  EXPECT_EQ(packets[3].sport, server_port) << PacketPrinter{packets};
+  EXPECT_EQ(packets[3].tcpFlags, FlagToHost(TCP_FLAG_SYN | TCP_FLAG_ACK))
+      << PacketPrinter{packets};
+}
+
+}  // namespace internal
+}  // namespace bpf
+}  // namespace android
diff --git a/service-t/native/libs/libnetworkstats/include/netdbpf/NetworkTraceHandler.h b/service-t/native/libs/libnetworkstats/include/netdbpf/NetworkTraceHandler.h
index c257aa0..80871c6 100644
--- a/service-t/native/libs/libnetworkstats/include/netdbpf/NetworkTraceHandler.h
+++ b/service-t/native/libs/libnetworkstats/include/netdbpf/NetworkTraceHandler.h
@@ -22,8 +22,7 @@
 #include <string>
 #include <unordered_map>
 
-#include "bpf/BpfMap.h"
-#include "bpf/BpfRingbuf.h"
+#include "netdbpf/NetworkTracePoller.h"
 
 // For PacketTrace struct definition
 #include "netd.h"
@@ -31,7 +30,39 @@
 namespace android {
 namespace bpf {
 
-class NetworkTraceHandler : public perfetto::DataSource<NetworkTraceHandler> {
+// BundleKeys are PacketTraces where timestamp and length are ignored.
+using BundleKey = PacketTrace;
+
+// BundleKeys are hashed using all fields except timestamp/length.
+struct BundleHash {
+  std::size_t operator()(const BundleKey& a) const;
+};
+
+// BundleKeys are equal if all fields except timestamp/length are equal.
+struct BundleEq {
+  bool operator()(const BundleKey& a, const BundleKey& b) const;
+};
+
+// Track the bundles we've interned and their corresponding intern id (iid). We
+// use IncrementalState (rather than state in the Handler) so that we stay in
+// sync with Perfetto's periodic state clearing (which helps recover from packet
+// loss). When state is cleared, the state object is replaced with a new default
+// constructed instance.
+struct NetworkTraceState {
+  bool cleared;
+  std::unordered_map<BundleKey, uint64_t, BundleHash, BundleEq> iids;
+};
+
+// Inject our custom incremental state type using type traits.
+struct NetworkTraceTraits : public perfetto::DefaultDataSourceTraits {
+  using IncrementalStateType = NetworkTraceState;
+};
+
+// NetworkTraceHandler implements the android.network_packets data source. This
+// class is registered with Perfetto and is instantiated when tracing starts and
+// destroyed when tracing ends. There is one instance per trace session.
+class NetworkTraceHandler
+    : public perfetto::DataSource<NetworkTraceHandler, NetworkTraceTraits> {
  public:
   // Registers this DataSource.
   static void RegisterDataSource();
@@ -39,45 +70,36 @@
   // Connects to the system Perfetto daemon and registers the trace handler.
   static void InitPerfettoTracing();
 
-  // Initialize with the default Perfetto callback.
-  NetworkTraceHandler();
-
-  // Testonly: initialize with a callback capable of intercepting data.
-  NetworkTraceHandler(std::function<void(const PacketTrace&)> callback)
-      : mCallback(std::move(callback)) {}
-
-  // Testonly: standalone functions without perfetto dependency.
-  bool Start();
-  bool Stop();
-  bool ConsumeAll();
-
   // perfetto::DataSource overrides:
-  void OnSetup(const SetupArgs&) override;
+  void OnSetup(const SetupArgs& args) override;
   void OnStart(const StartArgs&) override;
   void OnStop(const StopArgs&) override;
 
-  // Convert a PacketTrace into a Perfetto trace packet.
-  void Fill(const PacketTrace& src,
-            ::perfetto::protos::pbzero::TracePacket& dst);
+  // Writes the packets as Perfetto TracePackets, creating packets as needed
+  // using the provided callback (which allows easy testing).
+  void Write(const std::vector<PacketTrace>& packets,
+             NetworkTraceHandler::TraceContext& ctx);
 
  private:
-  void Loop();
+  // Convert a PacketTrace into a Perfetto trace packet.
+  void Fill(const PacketTrace& src,
+            ::perfetto::protos::pbzero::NetworkPacketEvent* event);
 
-  // How often to poll the ring buffer, defined by the trace config.
+  // Fills in contextual information either inline or via interning.
+  ::perfetto::protos::pbzero::NetworkPacketBundle* FillWithInterning(
+      NetworkTraceState* state, const BundleKey& key,
+      ::perfetto::protos::pbzero::TracePacket* dst);
+
+  static internal::NetworkTracePoller sPoller;
+  bool mStarted;
+
+  // Values from config, see proto for details.
   uint32_t mPollMs;
-
-  // The function to process PacketTrace, typically a Perfetto sink.
-  std::function<void(const PacketTrace&)> mCallback;
-
-  // The BPF ring buffer handle.
-  std::unique_ptr<BpfRingbuf<PacketTrace>> mRingBuffer;
-
-  // The packet tracing config map (really a 1-element array).
-  BpfMap<uint32_t, bool> mConfigurationMap;
-
-  // This must be the last member, causing it to be the first deleted. If it is
-  // not, members required for callbacks can be deleted before it's stopped.
-  std::unique_ptr<perfetto::base::TaskRunner> mTaskRunner;
+  uint32_t mInternLimit;
+  uint32_t mAggregationThreshold;
+  bool mDropLocalPort;
+  bool mDropRemotePort;
+  bool mDropTcpFlags;
 };
 
 }  // namespace bpf
diff --git a/service-t/native/libs/libnetworkstats/include/netdbpf/NetworkTracePoller.h b/service-t/native/libs/libnetworkstats/include/netdbpf/NetworkTracePoller.h
new file mode 100644
index 0000000..adde51e
--- /dev/null
+++ b/service-t/native/libs/libnetworkstats/include/netdbpf/NetworkTracePoller.h
@@ -0,0 +1,86 @@
+/**
+ * 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.
+ */
+
+#pragma once
+
+#include <perfetto/base/task_runner.h>
+#include <perfetto/tracing.h>
+
+#include <string>
+#include <unordered_map>
+
+#include "android-base/thread_annotations.h"
+#include "bpf/BpfMap.h"
+#include "bpf/BpfRingbuf.h"
+
+// For PacketTrace struct definition
+#include "netd.h"
+
+namespace android {
+namespace bpf {
+namespace internal {
+
+// NetworkTracePoller is responsible for interactions with the BPF ring buffer
+// including polling. This class is an internal helper for NetworkTraceHandler,
+// it is not meant to be used elsewhere.
+class NetworkTracePoller {
+ public:
+  using EventSink = std::function<void(const std::vector<PacketTrace>&)>;
+
+  // Testonly: initialize with a callback capable of intercepting data.
+  NetworkTracePoller(EventSink callback) : mCallback(std::move(callback)) {}
+
+  // Starts tracing with the given poll interval.
+  bool Start(uint32_t pollMs) EXCLUDES(mMutex);
+
+  // Stops tracing and release any held state.
+  bool Stop() EXCLUDES(mMutex);
+
+  // Consumes all available events from the ringbuffer.
+  bool ConsumeAll() EXCLUDES(mMutex);
+
+ private:
+  void SchedulePolling() REQUIRES(mMutex);
+  bool ConsumeAllLocked() REQUIRES(mMutex);
+
+  std::mutex mMutex;
+
+  // Records the number of successfully started active sessions so that only the
+  // first active session attempts setup and only the last cleans up. Note that
+  // the session count will remain zero if Start fails. It is expected that Stop
+  // will not be called for any trace session where Start fails.
+  int mSessionCount GUARDED_BY(mMutex);
+
+  // How often to poll the ring buffer, defined by the trace config.
+  uint32_t mPollMs GUARDED_BY(mMutex);
+
+  // The function to process PacketTrace, typically a Perfetto sink.
+  EventSink mCallback GUARDED_BY(mMutex);
+
+  // The BPF ring buffer handle.
+  std::unique_ptr<BpfRingbuf<PacketTrace>> mRingBuffer GUARDED_BY(mMutex);
+
+  // The packet tracing config map (really a 1-element array).
+  BpfMap<uint32_t, bool> mConfigurationMap GUARDED_BY(mMutex);
+
+  // This must be the last member, causing it to be the first deleted. If it is
+  // not, members required for callbacks can be deleted before it's stopped.
+  std::unique_ptr<perfetto::base::TaskRunner> mTaskRunner GUARDED_BY(mMutex);
+};
+
+}  // namespace internal
+}  // namespace bpf
+}  // namespace android
diff --git a/service-t/src/com/android/server/NsdService.java b/service-t/src/com/android/server/NsdService.java
index 4ad39e1..c92e9a9 100644
--- a/service-t/src/com/android/server/NsdService.java
+++ b/service-t/src/com/android/server/NsdService.java
@@ -20,6 +20,9 @@
 import static android.net.nsd.NsdManager.MDNS_DISCOVERY_MANAGER_EVENT;
 import static android.net.nsd.NsdManager.MDNS_SERVICE_EVENT;
 import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY;
+import static android.provider.DeviceConfig.NAMESPACE_TETHERING;
+
+import static com.android.modules.utils.build.SdkLevel.isAtLeastU;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -79,10 +82,10 @@
 import java.nio.charset.Charset;
 import java.nio.charset.CharsetEncoder;
 import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.Objects;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -110,6 +113,34 @@
      */
     private static final String MDNS_ADVERTISER_VERSION = "mdns_advertiser_version";
 
+    /**
+     * Comma-separated list of type:flag mappings indicating the flags to use to allowlist
+     * discovery/advertising using MdnsDiscoveryManager / MdnsAdvertiser for a given type.
+     *
+     * For example _mytype._tcp.local and _othertype._tcp.local would be configured with:
+     * _mytype._tcp:mytype,_othertype._tcp.local:othertype
+     *
+     * In which case the flags:
+     * "mdns_discovery_manager_allowlist_mytype_version",
+     * "mdns_advertiser_allowlist_mytype_version",
+     * "mdns_discovery_manager_allowlist_othertype_version",
+     * "mdns_advertiser_allowlist_othertype_version"
+     * would be used to toggle MdnsDiscoveryManager / MdnsAdvertiser for each type. The flags will
+     * be read with
+     * {@link DeviceConfigUtils#isFeatureEnabled(Context, String, String, String, boolean)}.
+     *
+     * @see #MDNS_DISCOVERY_MANAGER_ALLOWLIST_FLAG_PREFIX
+     * @see #MDNS_ADVERTISER_ALLOWLIST_FLAG_PREFIX
+     * @see #MDNS_ALLOWLIST_FLAG_SUFFIX
+     */
+    private static final String MDNS_TYPE_ALLOWLIST_FLAGS = "mdns_type_allowlist_flags";
+
+    private static final String MDNS_DISCOVERY_MANAGER_ALLOWLIST_FLAG_PREFIX =
+            "mdns_discovery_manager_allowlist_";
+    private static final String MDNS_ADVERTISER_ALLOWLIST_FLAG_PREFIX =
+            "mdns_advertiser_allowlist_";
+    private static final String MDNS_ALLOWLIST_FLAG_SUFFIX = "_version";
+
     public static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
     private static final long CLEANUP_DELAY_MS = 10000;
     private static final int IFACE_IDX_ANY = 0;
@@ -235,6 +266,35 @@
         }
     }
 
+    private class ServiceInfoListener extends MdnsListener {
+
+        ServiceInfoListener(int clientId, int transactionId, @NonNull NsdServiceInfo reqServiceInfo,
+                @NonNull String listenServiceType) {
+            super(clientId, transactionId, reqServiceInfo, listenServiceType);
+        }
+
+        @Override
+        public void onServiceFound(@NonNull MdnsServiceInfo serviceInfo) {
+            mNsdStateMachine.sendMessage(MDNS_DISCOVERY_MANAGER_EVENT, mTransactionId,
+                    NsdManager.SERVICE_UPDATED,
+                    new MdnsEvent(mClientId, mReqServiceInfo.getServiceType(), serviceInfo));
+        }
+
+        @Override
+        public void onServiceUpdated(@NonNull MdnsServiceInfo serviceInfo) {
+            mNsdStateMachine.sendMessage(MDNS_DISCOVERY_MANAGER_EVENT, mTransactionId,
+                    NsdManager.SERVICE_UPDATED,
+                    new MdnsEvent(mClientId, mReqServiceInfo.getServiceType(), serviceInfo));
+        }
+
+        @Override
+        public void onServiceRemoved(@NonNull MdnsServiceInfo serviceInfo) {
+            mNsdStateMachine.sendMessage(MDNS_DISCOVERY_MANAGER_EVENT, mTransactionId,
+                    NsdManager.SERVICE_UPDATED_LOST,
+                    new MdnsEvent(mClientId, mReqServiceInfo.getServiceType(), serviceInfo));
+        }
+    }
+
     /**
      * Data class of mdns service callback information.
      */
@@ -317,7 +377,7 @@
             if (!mIsMonitoringSocketsStarted) return;
             if (isAnyRequestActive()) return;
 
-            mMdnsSocketProvider.stopMonitoringSockets();
+            mMdnsSocketProvider.requestStopWhenInactive();
             mIsMonitoringSocketsStarted = false;
         }
 
@@ -491,37 +551,6 @@
                 mIdToClientInfoMap.put(globalId, clientInfo);
             }
 
-            private void clearRegisteredServiceInfo(ClientInfo clientInfo) {
-                clientInfo.mRegisteredService = null;
-                clientInfo.mClientIdForServiceUpdates = 0;
-            }
-
-            /**
-             * Check the given service type is valid and construct it to a service type
-             * which can use for discovery / resolution service.
-             *
-             * <p> The valid service type should be 2 labels, or 3 labels if the query is for a
-             * subtype (see RFC6763 7.1). Each label is up to 63 characters and must start with an
-             * underscore; they are alphanumerical characters or dashes or underscore, except the
-             * last one that is just alphanumerical. The last label must be _tcp or _udp.
-             *
-             * @param serviceType the request service type for discovery / resolution service
-             * @return constructed service type or null if the given service type is invalid.
-             */
-            @Nullable
-            private String constructServiceType(String serviceType) {
-                if (TextUtils.isEmpty(serviceType)) return null;
-
-                final Pattern serviceTypePattern = Pattern.compile(
-                        "^(_[a-zA-Z0-9-_]{1,61}[a-zA-Z0-9]\\.)?"
-                                + "(_[a-zA-Z0-9-_]{1,61}[a-zA-Z0-9]\\._(?:tcp|udp))$");
-                final Matcher matcher = serviceTypePattern.matcher(serviceType);
-                if (!matcher.matches()) return null;
-                return matcher.group(1) == null
-                        ? serviceType
-                        : matcher.group(1) + "_sub." + matcher.group(2);
-            }
-
             /**
              * Truncate a service name to up to 63 UTF-8 bytes.
              *
@@ -545,6 +574,12 @@
                 return new String(out.array(), 0, out.position(), utf8);
             }
 
+            private void stopDiscoveryManagerRequest(ClientRequest request, int clientId, int id,
+                    ClientInfo clientInfo) {
+                clientInfo.unregisterMdnsListenerFromRequest(request);
+                removeRequestMap(clientId, id, clientInfo);
+            }
+
             @Override
             public boolean processMessage(Message msg) {
                 final ClientInfo clientInfo;
@@ -572,8 +607,9 @@
 
                         final NsdServiceInfo info = args.serviceInfo;
                         id = getUniqueId();
-                        if (mDeps.isMdnsDiscoveryManagerEnabled(mContext)) {
-                            final String serviceType = constructServiceType(info.getServiceType());
+                        final String serviceType = constructServiceType(info.getServiceType());
+                        if (mDeps.isMdnsDiscoveryManagerEnabled(mContext)
+                                || useDiscoveryManagerForType(serviceType)) {
                             if (serviceType == null) {
                                 clientInfo.onDiscoverServicesFailed(clientId,
                                         NsdManager.FAILURE_INTERNAL_ERROR);
@@ -631,11 +667,7 @@
                         // point, so this needs to check the type of the original request to
                         // unregister instead of looking at the flag value.
                         if (request instanceof DiscoveryManagerRequest) {
-                            final MdnsListener listener =
-                                    ((DiscoveryManagerRequest) request).mListener;
-                            mMdnsDiscoveryManager.unregisterListener(
-                                    listener.getListenedServiceType(), listener);
-                            removeRequestMap(clientId, id, clientInfo);
+                            stopDiscoveryManagerRequest(request, clientId, id, clientInfo);
                             clientInfo.onStopDiscoverySucceeded(clientId);
                         } else {
                             removeRequestMap(clientId, id, clientInfo);
@@ -667,10 +699,11 @@
                         }
 
                         id = getUniqueId();
-                        if (mDeps.isMdnsAdvertiserEnabled(mContext)) {
-                            final NsdServiceInfo serviceInfo = args.serviceInfo;
-                            final String serviceType = serviceInfo.getServiceType();
-                            final String registerServiceType = constructServiceType(serviceType);
+                        final NsdServiceInfo serviceInfo = args.serviceInfo;
+                        final String serviceType = serviceInfo.getServiceType();
+                        final String registerServiceType = constructServiceType(serviceType);
+                        if (mDeps.isMdnsAdvertiserEnabled(mContext)
+                                || useAdvertiserForType(registerServiceType)) {
                             if (registerServiceType == null) {
                                 Log.e(TAG, "Invalid service type: " + serviceType);
                                 clientInfo.onRegisterServiceFailed(clientId,
@@ -686,7 +719,7 @@
                             storeAdvertiserRequestMap(clientId, id, clientInfo);
                         } else {
                             maybeStartDaemon();
-                            if (registerService(id, args.serviceInfo)) {
+                            if (registerService(id, serviceInfo)) {
                                 if (DBG) Log.d(TAG, "Register " + clientId + " " + id);
                                 storeLegacyRequestMap(clientId, id, clientInfo, msg.what);
                                 // Return success after mDns reports success
@@ -748,8 +781,9 @@
 
                         final NsdServiceInfo info = args.serviceInfo;
                         id = getUniqueId();
-                        if (mDeps.isMdnsDiscoveryManagerEnabled(mContext)) {
-                            final String serviceType = constructServiceType(info.getServiceType());
+                        final String serviceType = constructServiceType(info.getServiceType());
+                        if (mDeps.isMdnsDiscoveryManagerEnabled(mContext)
+                                || useDiscoveryManagerForType(serviceType)) {
                             if (serviceType == null) {
                                 clientInfo.onResolveServiceFailed(clientId,
                                         NsdManager.FAILURE_INTERNAL_ERROR);
@@ -763,6 +797,7 @@
                             final MdnsSearchOptions options = MdnsSearchOptions.newBuilder()
                                     .setNetwork(info.getNetwork())
                                     .setIsPassiveMode(true)
+                                    .setResolveInstanceName(info.getServiceName())
                                     .build();
                             mMdnsDiscoveryManager.registerListener(
                                     resolveServiceType, listener, options);
@@ -775,7 +810,7 @@
                             }
 
                             maybeStartDaemon();
-                            if (resolveService(id, args.serviceInfo)) {
+                            if (resolveService(id, info)) {
                                 clientInfo.mResolvedService = new NsdServiceInfo();
                                 storeLegacyRequestMap(clientId, id, clientInfo, msg.what);
                             } else {
@@ -803,18 +838,25 @@
                             break;
                         }
                         id = request.mGlobalId;
-                        removeRequestMap(clientId, id, clientInfo);
-                        if (stopResolveService(id)) {
+                        // Note isMdnsDiscoveryManagerEnabled may have changed to false at this
+                        // point, so this needs to check the type of the original request to
+                        // unregister instead of looking at the flag value.
+                        if (request instanceof DiscoveryManagerRequest) {
+                            stopDiscoveryManagerRequest(request, clientId, id, clientInfo);
                             clientInfo.onStopResolutionSucceeded(clientId);
                         } else {
-                            clientInfo.onStopResolutionFailed(
-                                    clientId, NsdManager.FAILURE_OPERATION_NOT_RUNNING);
+                            removeRequestMap(clientId, id, clientInfo);
+                            if (stopResolveService(id)) {
+                                clientInfo.onStopResolutionSucceeded(clientId);
+                            } else {
+                                clientInfo.onStopResolutionFailed(
+                                        clientId, NsdManager.FAILURE_OPERATION_NOT_RUNNING);
+                            }
+                            clientInfo.mResolvedService = null;
                         }
-                        clientInfo.mResolvedService = null;
-                        // TODO: Implement the stop resolution with MdnsDiscoveryManager.
                         break;
                     }
-                    case NsdManager.REGISTER_SERVICE_CALLBACK:
+                    case NsdManager.REGISTER_SERVICE_CALLBACK: {
                         if (DBG) Log.d(TAG, "Register a service callback");
                         args = (ListenerArgs) msg.obj;
                         clientInfo = mClients.get(args.connector);
@@ -826,23 +868,29 @@
                             break;
                         }
 
-                        if (clientInfo.mRegisteredService != null) {
-                            clientInfo.onServiceInfoCallbackRegistrationFailed(
-                                    clientId, NsdManager.FAILURE_ALREADY_ACTIVE);
+                        final NsdServiceInfo info = args.serviceInfo;
+                        id = getUniqueId();
+                        final String serviceType = constructServiceType(info.getServiceType());
+                        if (serviceType == null) {
+                            clientInfo.onServiceInfoCallbackRegistrationFailed(clientId,
+                                    NsdManager.FAILURE_BAD_PARAMETERS);
                             break;
                         }
+                        final String resolveServiceType = serviceType + ".local";
 
-                        maybeStartDaemon();
-                        id = getUniqueId();
-                        if (resolveService(id, args.serviceInfo)) {
-                            clientInfo.mRegisteredService = new NsdServiceInfo();
-                            clientInfo.mClientIdForServiceUpdates = clientId;
-                            storeLegacyRequestMap(clientId, id, clientInfo, msg.what);
-                        } else {
-                            clientInfo.onServiceInfoCallbackRegistrationFailed(
-                                    clientId, NsdManager.FAILURE_BAD_PARAMETERS);
-                        }
+                        maybeStartMonitoringSockets();
+                        final MdnsListener listener =
+                                new ServiceInfoListener(clientId, id, info, resolveServiceType);
+                        final MdnsSearchOptions options = MdnsSearchOptions.newBuilder()
+                                .setNetwork(info.getNetwork())
+                                .setIsPassiveMode(true)
+                                .setResolveInstanceName(info.getServiceName())
+                                .build();
+                        mMdnsDiscoveryManager.registerListener(
+                                resolveServiceType, listener, options);
+                        storeDiscoveryManagerRequestMap(clientId, id, listener, clientInfo);
                         break;
+                    }
                     case NsdManager.UNREGISTER_SERVICE_CALLBACK: {
                         if (DBG) Log.d(TAG, "Unregister a service callback");
                         args = (ListenerArgs) msg.obj;
@@ -857,17 +905,16 @@
 
                         final ClientRequest request = clientInfo.mClientRequests.get(clientId);
                         if (request == null) {
-                            Log.e(TAG, "Unknown client request in STOP_RESOLUTION");
+                            Log.e(TAG, "Unknown client request in UNREGISTER_SERVICE_CALLBACK");
                             break;
                         }
                         id = request.mGlobalId;
-                        removeRequestMap(clientId, id, clientInfo);
-                        if (stopResolveService(id)) {
+                        if (request instanceof DiscoveryManagerRequest) {
+                            stopDiscoveryManagerRequest(request, clientId, id, clientInfo);
                             clientInfo.onServiceInfoCallbackUnregistered(clientId);
                         } else {
-                            Log.e(TAG, "Failed to unregister service info callback");
+                            loge("Unregister failed with non-DiscoveryManagerRequest.");
                         }
-                        clearRegisteredServiceInfo(clientInfo);
                         break;
                     }
                     case MDNS_SERVICE_EVENT:
@@ -886,19 +933,6 @@
                 return HANDLED;
             }
 
-            private void notifyResolveFailedResult(boolean isListenedToUpdates, int clientId,
-                    ClientInfo clientInfo, int error) {
-                if (isListenedToUpdates) {
-                    clientInfo.onServiceInfoCallbackRegistrationFailed(clientId, error);
-                    clearRegisteredServiceInfo(clientInfo);
-                } else {
-                    // The resolve API always returned FAILURE_INTERNAL_ERROR on error; keep it
-                    // for backwards compatibility.
-                    clientInfo.onResolveServiceFailed(clientId, NsdManager.FAILURE_INTERNAL_ERROR);
-                    clientInfo.mResolvedService = null;
-                }
-            }
-
             private boolean handleMDnsServiceEvent(int code, int id, Object obj) {
                 NsdServiceInfo servInfo;
                 ClientInfo clientInfo = mIdToClientInfoMap.get(id);
@@ -955,8 +989,6 @@
                         // found services on the same interface index and their network at the time
                         setServiceNetworkForCallback(servInfo, lostNetId, info.interfaceIdx);
                         clientInfo.onServiceLost(clientId, servInfo);
-                        // TODO: also support registered service lost when not discovering
-                        clientInfo.maybeNotifyRegisteredServiceLost(servInfo);
                         break;
                     }
                     case IMDnsEventListener.SERVICE_DISCOVERY_FAILED:
@@ -993,11 +1025,7 @@
                         String rest = fullName.substring(index);
                         String type = rest.replace(".local.", "");
 
-                        final boolean isListenedToUpdates =
-                                clientId == clientInfo.mClientIdForServiceUpdates;
-                        final NsdServiceInfo serviceInfo = isListenedToUpdates
-                                ? clientInfo.mRegisteredService : clientInfo.mResolvedService;
-
+                        final NsdServiceInfo serviceInfo = clientInfo.mResolvedService;
                         serviceInfo.setServiceName(name);
                         serviceInfo.setServiceType(type);
                         serviceInfo.setPort(info.port);
@@ -1012,8 +1040,9 @@
                             storeLegacyRequestMap(clientId, id2, clientInfo,
                                     NsdManager.RESOLVE_SERVICE);
                         } else {
-                            notifyResolveFailedResult(isListenedToUpdates, clientId, clientInfo,
-                                    NsdManager.FAILURE_BAD_PARAMETERS);
+                            clientInfo.onResolveServiceFailed(
+                                    clientId, NsdManager.FAILURE_INTERNAL_ERROR);
+                            clientInfo.mResolvedService = null;
                         }
                         break;
                     }
@@ -1021,17 +1050,17 @@
                         /* NNN resolveId errorCode */
                         stopResolveService(id);
                         removeRequestMap(clientId, id, clientInfo);
-                        notifyResolveFailedResult(
-                                clientId == clientInfo.mClientIdForServiceUpdates,
-                                clientId, clientInfo, NsdManager.FAILURE_BAD_PARAMETERS);
+                        clientInfo.onResolveServiceFailed(
+                                clientId, NsdManager.FAILURE_INTERNAL_ERROR);
+                        clientInfo.mResolvedService = null;
                         break;
                     case IMDnsEventListener.SERVICE_GET_ADDR_FAILED:
                         /* NNN resolveId errorCode */
                         stopGetAddrInfo(id);
                         removeRequestMap(clientId, id, clientInfo);
-                        notifyResolveFailedResult(
-                                clientId == clientInfo.mClientIdForServiceUpdates,
-                                clientId, clientInfo, NsdManager.FAILURE_BAD_PARAMETERS);
+                        clientInfo.onResolveServiceFailed(
+                                clientId, NsdManager.FAILURE_INTERNAL_ERROR);
+                        clientInfo.mResolvedService = null;
                         break;
                     case IMDnsEventListener.SERVICE_GET_ADDR_SUCCESS: {
                         /* NNN resolveId hostname ttl addr interfaceIdx netId */
@@ -1048,38 +1077,19 @@
                         // If the resolved service is on an interface without a network, consider it
                         // as a failure: it would not be usable by apps as they would need
                         // privileged permissions.
-                        if (clientId == clientInfo.mClientIdForServiceUpdates) {
-                            if (netId != NETID_UNSET && serviceHost != null) {
-                                setServiceNetworkForCallback(clientInfo.mRegisteredService,
-                                        netId, info.interfaceIdx);
-                                final List<InetAddress> addresses =
-                                        clientInfo.mRegisteredService.getHostAddresses();
-                                addresses.add(serviceHost);
-                                clientInfo.mRegisteredService.setHostAddresses(addresses);
-                                clientInfo.onServiceUpdated(
-                                        clientId, clientInfo.mRegisteredService);
-                            } else {
-                                stopGetAddrInfo(id);
-                                removeRequestMap(clientId, id, clientInfo);
-                                clearRegisteredServiceInfo(clientInfo);
-                                clientInfo.onServiceInfoCallbackRegistrationFailed(
-                                        clientId, NsdManager.FAILURE_BAD_PARAMETERS);
-                            }
+                        if (netId != NETID_UNSET && serviceHost != null) {
+                            clientInfo.mResolvedService.setHost(serviceHost);
+                            setServiceNetworkForCallback(clientInfo.mResolvedService,
+                                    netId, info.interfaceIdx);
+                            clientInfo.onResolveServiceSucceeded(
+                                    clientId, clientInfo.mResolvedService);
                         } else {
-                            if (netId != NETID_UNSET && serviceHost != null) {
-                                clientInfo.mResolvedService.setHost(serviceHost);
-                                setServiceNetworkForCallback(clientInfo.mResolvedService,
-                                        netId, info.interfaceIdx);
-                                clientInfo.onResolveServiceSucceeded(
-                                        clientId, clientInfo.mResolvedService);
-                            } else {
-                                clientInfo.onResolveServiceFailed(
-                                        clientId, NsdManager.FAILURE_INTERNAL_ERROR);
-                            }
-                            stopGetAddrInfo(id);
-                            removeRequestMap(clientId, id, clientInfo);
-                            clientInfo.mResolvedService = null;
+                            clientInfo.onResolveServiceFailed(
+                                    clientId, NsdManager.FAILURE_INTERNAL_ERROR);
                         }
+                        stopGetAddrInfo(id);
+                        removeRequestMap(clientId, id, clientInfo);
+                        clientInfo.mResolvedService = null;
                         break;
                     }
                     default:
@@ -1144,17 +1154,27 @@
                                 Log.e(TAG, "Invalid attribute", e);
                             }
                         }
-                        try {
-                            if (serviceInfo.getIpv4Address() != null) {
-                                info.setHost(InetAddresses.parseNumericAddress(
-                                        serviceInfo.getIpv4Address()));
-                            } else {
-                                info.setHost(InetAddresses.parseNumericAddress(
-                                        serviceInfo.getIpv6Address()));
+                        final List<InetAddress> addresses = new ArrayList<>();
+                        for (String ipv4Address : serviceInfo.getIpv4Addresses()) {
+                            try {
+                                addresses.add(InetAddresses.parseNumericAddress(ipv4Address));
+                            } catch (IllegalArgumentException e) {
+                                Log.wtf(TAG, "Invalid ipv4 address", e);
                             }
+                        }
+                        for (String ipv6Address : serviceInfo.getIpv6Addresses()) {
+                            try {
+                                addresses.add(InetAddresses.parseNumericAddress(ipv6Address));
+                            } catch (IllegalArgumentException e) {
+                                Log.wtf(TAG, "Invalid ipv6 address", e);
+                            }
+                        }
+
+                        if (addresses.size() != 0) {
+                            info.setHostAddresses(addresses);
                             clientInfo.onResolveServiceSucceeded(clientId, info);
-                        } catch (IllegalArgumentException e) {
-                            Log.wtf(TAG, "Invalid address in RESOLVE_SERVICE_SUCCEEDED", e);
+                        } else {
+                            // No address. Notify resolution failure.
                             clientInfo.onResolveServiceFailed(
                                     clientId, NsdManager.FAILURE_INTERNAL_ERROR);
                         }
@@ -1164,12 +1184,45 @@
                             Log.wtf(TAG, "non-DiscoveryManager request in DiscoveryManager event");
                             break;
                         }
-                        final MdnsListener listener = ((DiscoveryManagerRequest) request).mListener;
-                        mMdnsDiscoveryManager.unregisterListener(
-                                listener.getListenedServiceType(), listener);
-                        removeRequestMap(clientId, transactionId, clientInfo);
+                        stopDiscoveryManagerRequest(request, clientId, transactionId, clientInfo);
                         break;
                     }
+                    case NsdManager.SERVICE_UPDATED: {
+                        final MdnsServiceInfo serviceInfo = event.mMdnsServiceInfo;
+                        info.setPort(serviceInfo.getPort());
+
+                        Map<String, String> attrs = serviceInfo.getAttributes();
+                        for (Map.Entry<String, String> kv : attrs.entrySet()) {
+                            final String key = kv.getKey();
+                            try {
+                                info.setAttribute(key, serviceInfo.getAttributeAsBytes(key));
+                            } catch (IllegalArgumentException e) {
+                                Log.e(TAG, "Invalid attribute", e);
+                            }
+                        }
+
+                        final List<InetAddress> addresses = new ArrayList<>();
+                        for (String ipv4Address : serviceInfo.getIpv4Addresses()) {
+                            try {
+                                addresses.add(InetAddresses.parseNumericAddress(ipv4Address));
+                            } catch (IllegalArgumentException e) {
+                                Log.wtf(TAG, "Invalid ipv4 address", e);
+                            }
+                        }
+                        for (String ipv6Address : serviceInfo.getIpv6Addresses()) {
+                            try {
+                                addresses.add(InetAddresses.parseNumericAddress(ipv6Address));
+                            } catch (IllegalArgumentException e) {
+                                Log.wtf(TAG, "Invalid ipv6 address", e);
+                            }
+                        }
+                        info.setHostAddresses(addresses);
+                        clientInfo.onServiceUpdated(clientId, info);
+                        break;
+                    }
+                    case NsdManager.SERVICE_UPDATED_LOST:
+                        clientInfo.onServiceUpdatedLost(clientId);
+                        break;
                     default:
                         return false;
                 }
@@ -1227,6 +1280,34 @@
         return sb.toString();
     }
 
+    /**
+     * Check the given service type is valid and construct it to a service type
+     * which can use for discovery / resolution service.
+     *
+     * <p> The valid service type should be 2 labels, or 3 labels if the query is for a
+     * subtype (see RFC6763 7.1). Each label is up to 63 characters and must start with an
+     * underscore; they are alphanumerical characters or dashes or underscore, except the
+     * last one that is just alphanumerical. The last label must be _tcp or _udp.
+     *
+     * @param serviceType the request service type for discovery / resolution service
+     * @return constructed service type or null if the given service type is invalid.
+     */
+    @Nullable
+    public static String constructServiceType(String serviceType) {
+        if (TextUtils.isEmpty(serviceType)) return null;
+
+        final Pattern serviceTypePattern = Pattern.compile(
+                "^(_[a-zA-Z0-9-_]{1,61}[a-zA-Z0-9]\\.)?"
+                        + "(_[a-zA-Z0-9-_]{1,61}[a-zA-Z0-9]\\._(?:tcp|udp))"
+                        // Drop '.' at the end of service type that is compatible with old backend.
+                        + "\\.?$");
+        final Matcher matcher = serviceTypePattern.matcher(serviceType);
+        if (!matcher.matches()) return null;
+        return matcher.group(1) == null
+                ? matcher.group(2)
+                : matcher.group(1) + "_sub." + matcher.group(2);
+    }
+
     @VisibleForTesting
     NsdService(Context ctx, Handler handler, long cleanupDelayMs) {
         this(ctx, handler, cleanupDelayMs, new Dependencies());
@@ -1264,8 +1345,9 @@
          * @return true if the MdnsDiscoveryManager feature is enabled.
          */
         public boolean isMdnsDiscoveryManagerEnabled(Context context) {
-            return DeviceConfigUtils.isFeatureEnabled(context, NAMESPACE_CONNECTIVITY,
-                    MDNS_DISCOVERY_MANAGER_VERSION, false /* defaultEnabled */);
+            return isAtLeastU() || DeviceConfigUtils.isFeatureEnabled(context,
+                    NAMESPACE_CONNECTIVITY, MDNS_DISCOVERY_MANAGER_VERSION,
+                    false /* defaultEnabled */);
         }
 
         /**
@@ -1275,8 +1357,26 @@
          * @return true if the MdnsAdvertiser feature is enabled.
          */
         public boolean isMdnsAdvertiserEnabled(Context context) {
-            return DeviceConfigUtils.isFeatureEnabled(context, NAMESPACE_CONNECTIVITY,
-                    MDNS_ADVERTISER_VERSION, false /* defaultEnabled */);
+            return isAtLeastU() || DeviceConfigUtils.isFeatureEnabled(context,
+                    NAMESPACE_CONNECTIVITY, MDNS_ADVERTISER_VERSION, false /* defaultEnabled */);
+        }
+
+        /**
+         * Get the type allowlist flag value.
+         * @see #MDNS_TYPE_ALLOWLIST_FLAGS
+         */
+        @Nullable
+        public String getTypeAllowlistFlags() {
+            return DeviceConfigUtils.getDeviceConfigProperty(NAMESPACE_TETHERING,
+                    MDNS_TYPE_ALLOWLIST_FLAGS, null);
+        }
+
+        /**
+         * @see DeviceConfigUtils#isFeatureEnabled(Context, String, String, String, boolean)
+         */
+        public boolean isFeatureEnabled(Context context, String feature) {
+            return DeviceConfigUtils.isFeatureEnabled(context, NAMESPACE_TETHERING,
+                    feature, DeviceConfigUtils.TETHERING_MODULE_NAME, false /* defaultEnabled */);
         }
 
         /**
@@ -1304,6 +1404,41 @@
         }
     }
 
+    /**
+     * Return whether a type is allowlisted to use the Java backend.
+     * @param type The service type
+     * @param flagPrefix One of {@link #MDNS_ADVERTISER_ALLOWLIST_FLAG_PREFIX} or
+     *                   {@link #MDNS_DISCOVERY_MANAGER_ALLOWLIST_FLAG_PREFIX}.
+     */
+    private boolean isTypeAllowlistedForJavaBackend(@Nullable String type,
+            @NonNull String flagPrefix) {
+        if (type == null) return false;
+        final String typesConfig = mDeps.getTypeAllowlistFlags();
+        if (TextUtils.isEmpty(typesConfig)) return false;
+
+        final String mappingPrefix = type + ":";
+        String mappedFlag = null;
+        for (String mapping : TextUtils.split(typesConfig, ",")) {
+            if (mapping.startsWith(mappingPrefix)) {
+                mappedFlag = mapping.substring(mappingPrefix.length());
+                break;
+            }
+        }
+
+        if (mappedFlag == null) return false;
+
+        return mDeps.isFeatureEnabled(mContext,
+                flagPrefix + mappedFlag + MDNS_ALLOWLIST_FLAG_SUFFIX);
+    }
+
+    private boolean useDiscoveryManagerForType(@Nullable String type) {
+        return isTypeAllowlistedForJavaBackend(type, MDNS_DISCOVERY_MANAGER_ALLOWLIST_FLAG_PREFIX);
+    }
+
+    private boolean useAdvertiserForType(@Nullable String type) {
+        return isTypeAllowlistedForJavaBackend(type, MDNS_ADVERTISER_ALLOWLIST_FLAG_PREFIX);
+    }
+
     public static NsdService create(Context context) {
         HandlerThread thread = new HandlerThread(TAG);
         thread.start();
@@ -1659,11 +1794,6 @@
         // The target SDK of this client < Build.VERSION_CODES.S
         private boolean mIsPreSClient = false;
 
-        /*** The service that is registered to listen to its updates */
-        private NsdServiceInfo mRegisteredService;
-        /*** The client id that listen to updates */
-        private int mClientIdForServiceUpdates;
-
         private ClientInfo(INsdManagerCallback cb) {
             mCb = cb;
             if (DBG) Log.d(TAG, "New client");
@@ -1694,6 +1824,13 @@
             mIsPreSClient = true;
         }
 
+        private void unregisterMdnsListenerFromRequest(ClientRequest request) {
+            final MdnsListener listener =
+                    ((DiscoveryManagerRequest) request).mListener;
+            mMdnsDiscoveryManager.unregisterListener(
+                    listener.getListenedServiceType(), listener);
+        }
+
         // Remove any pending requests from the global map when we get rid of a client,
         // and send cancellations to the daemon.
         private void expungeAllRequests() {
@@ -1709,10 +1846,7 @@
                 }
 
                 if (request instanceof DiscoveryManagerRequest) {
-                    final MdnsListener listener =
-                            ((DiscoveryManagerRequest) request).mListener;
-                    mMdnsDiscoveryManager.unregisterListener(
-                            listener.getListenedServiceType(), listener);
+                    unregisterMdnsListenerFromRequest(request);
                     continue;
                 }
 
@@ -1754,18 +1888,6 @@
             return -1;
         }
 
-        private void maybeNotifyRegisteredServiceLost(@NonNull NsdServiceInfo info) {
-            if (mRegisteredService == null) return;
-            if (!Objects.equals(mRegisteredService.getServiceName(), info.getServiceName())) return;
-            // Resolved services have a leading dot appended at the beginning of their type, but in
-            // discovered info it's at the end
-            if (!Objects.equals(
-                    mRegisteredService.getServiceType() + ".", "." + info.getServiceType())) {
-                return;
-            }
-            onServiceUpdatedLost(mClientIdForServiceUpdates);
-        }
-
         void onDiscoverServicesStarted(int listenerKey, NsdServiceInfo info) {
             try {
                 mCb.onDiscoverServicesStarted(listenerKey, info);
diff --git a/service-t/src/com/android/server/connectivity/mdns/EnqueueMdnsQueryCallable.java b/service-t/src/com/android/server/connectivity/mdns/EnqueueMdnsQueryCallable.java
index fdd1478..9a67007 100644
--- a/service-t/src/com/android/server/connectivity/mdns/EnqueueMdnsQueryCallable.java
+++ b/service-t/src/com/android/server/connectivity/mdns/EnqueueMdnsQueryCallable.java
@@ -60,13 +60,21 @@
         }
     }
 
+    @NonNull
     private final WeakReference<MdnsSocketClientBase> weakRequestSender;
+    @NonNull
     private final MdnsPacketWriter packetWriter;
+    @NonNull
     private final String[] serviceTypeLabels;
+    @NonNull
     private final List<String> subtypes;
     private final boolean expectUnicastResponse;
     private final int transactionId;
+    @Nullable
     private final Network network;
+    private final boolean sendDiscoveryQueries;
+    @NonNull
+    private final List<MdnsResponse> servicesToResolve;
 
     EnqueueMdnsQueryCallable(
             @NonNull MdnsSocketClientBase requestSender,
@@ -75,7 +83,9 @@
             @NonNull Collection<String> subtypes,
             boolean expectUnicastResponse,
             int transactionId,
-            @Nullable Network network) {
+            @Nullable Network network,
+            boolean sendDiscoveryQueries,
+            @NonNull Collection<MdnsResponse> servicesToResolve) {
         weakRequestSender = new WeakReference<>(requestSender);
         this.packetWriter = packetWriter;
         serviceTypeLabels = TextUtils.split(serviceType, "\\.");
@@ -83,6 +93,8 @@
         this.expectUnicastResponse = expectUnicastResponse;
         this.transactionId = transactionId;
         this.network = network;
+        this.sendDiscoveryQueries = sendDiscoveryQueries;
+        this.servicesToResolve = new ArrayList<>(servicesToResolve);
     }
 
     // Incompatible return type for override of Callable#call().
@@ -96,9 +108,44 @@
                 return null;
             }
 
-            int numQuestions = 1;
-            if (!subtypes.isEmpty()) {
-                numQuestions += subtypes.size();
+            int numQuestions = 0;
+
+            if (sendDiscoveryQueries) {
+                numQuestions++; // Base service type
+                if (!subtypes.isEmpty()) {
+                    numQuestions += subtypes.size();
+                }
+            }
+
+            // List of (name, type) to query
+            final ArrayList<Pair<String[], Integer>> missingKnownAnswerRecords = new ArrayList<>();
+            for (MdnsResponse response : servicesToResolve) {
+                // TODO: also send queries to renew record TTL (as per RFC6762 7.1 no need to query
+                // if remaining TTL is more than half the original one, so send the queries if half
+                // the TTL has passed).
+                if (response.isComplete()) continue;
+                final String[] serviceName = response.getServiceName();
+                if (serviceName == null) continue;
+                if (!response.hasTextRecord()) {
+                    missingKnownAnswerRecords.add(new Pair<>(serviceName, MdnsRecord.TYPE_TXT));
+                }
+                if (!response.hasServiceRecord()) {
+                    missingKnownAnswerRecords.add(new Pair<>(serviceName, MdnsRecord.TYPE_SRV));
+                    // The hostname is not yet known, so queries for address records will be sent
+                    // the next time the EnqueueMdnsQueryCallable is enqueued if the reply does not
+                    // contain them. In practice, advertisers should include the address records
+                    // when queried for SRV, although it's not a MUST requirement (RFC6763 12.2).
+                } else if (!response.hasInet4AddressRecord() && !response.hasInet6AddressRecord()) {
+                    final String[] host = response.getServiceRecord().getServiceHost();
+                    missingKnownAnswerRecords.add(new Pair<>(host, MdnsRecord.TYPE_A));
+                    missingKnownAnswerRecords.add(new Pair<>(host, MdnsRecord.TYPE_AAAA));
+                }
+            }
+            numQuestions += missingKnownAnswerRecords.size();
+
+            if (numQuestions == 0) {
+                // No query to send
+                return null;
             }
 
             // Header.
@@ -109,28 +156,25 @@
             packetWriter.writeUInt16(0); // number of authority entries
             packetWriter.writeUInt16(0); // number of additional records
 
-            // Question(s). There will be one question for each (fqdn+subtype, recordType)
-          // combination,
-            // as well as one for each (fqdn, recordType) combination.
-
-            for (String subtype : subtypes) {
-                String[] labels = new String[serviceTypeLabels.length + 2];
-                labels[0] = MdnsConstants.SUBTYPE_PREFIX + subtype;
-                labels[1] = MdnsConstants.SUBTYPE_LABEL;
-                System.arraycopy(serviceTypeLabels, 0, labels, 2, serviceTypeLabels.length);
-
-                packetWriter.writeLabels(labels);
-                packetWriter.writeUInt16(MdnsRecord.TYPE_PTR);
-                packetWriter.writeUInt16(
-                        MdnsConstants.QCLASS_INTERNET
-                                | (expectUnicastResponse ? MdnsConstants.QCLASS_UNICAST : 0));
+            // Question(s) for missing records on known answers
+            for (Pair<String[], Integer> question : missingKnownAnswerRecords) {
+                writeQuestion(question.first, question.second);
             }
 
-            packetWriter.writeLabels(serviceTypeLabels);
-            packetWriter.writeUInt16(MdnsRecord.TYPE_PTR);
-            packetWriter.writeUInt16(
-                    MdnsConstants.QCLASS_INTERNET
-                            | (expectUnicastResponse ? MdnsConstants.QCLASS_UNICAST : 0));
+            // Question(s) for discovering other services with the type. There will be one question
+            // for each (fqdn+subtype, recordType) combination, as well as one for each (fqdn,
+            // recordType) combination.
+            if (sendDiscoveryQueries) {
+                for (String subtype : subtypes) {
+                    String[] labels = new String[serviceTypeLabels.length + 2];
+                    labels[0] = MdnsConstants.SUBTYPE_PREFIX + subtype;
+                    labels[1] = MdnsConstants.SUBTYPE_LABEL;
+                    System.arraycopy(serviceTypeLabels, 0, labels, 2, serviceTypeLabels.length);
+
+                    writeQuestion(labels, MdnsRecord.TYPE_PTR);
+                }
+                writeQuestion(serviceTypeLabels, MdnsRecord.TYPE_PTR);
+            }
 
             if (requestSender instanceof MdnsMultinetworkSocketClient) {
                 sendPacketToIpv4AndIpv6(requestSender, MdnsConstants.MDNS_PORT, network);
@@ -159,6 +203,14 @@
         }
     }
 
+    private void writeQuestion(String[] labels, int type) throws IOException {
+        packetWriter.writeLabels(labels);
+        packetWriter.writeUInt16(type);
+        packetWriter.writeUInt16(
+                MdnsConstants.QCLASS_INTERNET
+                        | (expectUnicastResponse ? MdnsConstants.QCLASS_UNICAST : 0));
+    }
+
     private void sendPacketTo(MdnsSocketClientBase requestSender, InetSocketAddress address)
             throws IOException {
         DatagramPacket packet = packetWriter.getPacket(address);
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsAdvertiser.java b/service-t/src/com/android/server/connectivity/mdns/MdnsAdvertiser.java
index 977478a..ec3e997 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsAdvertiser.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsAdvertiser.java
@@ -31,6 +31,7 @@
 
 import java.util.List;
 import java.util.Map;
+import java.util.UUID;
 import java.util.function.BiPredicate;
 import java.util.function.Consumer;
 
@@ -43,6 +44,9 @@
     private static final String TAG = MdnsAdvertiser.class.getSimpleName();
     static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
 
+    // Top-level domain for link-local queries, as per RFC6762 3.
+    private static final String LOCAL_TLD = "local";
+
     private final Looper mLooper;
     private final AdvertiserCallback mCb;
 
@@ -60,6 +64,8 @@
     private final SparseArray<Registration> mRegistrations = new SparseArray<>();
     private final Dependencies mDeps;
 
+    private String[] mDeviceHostName;
+
     /**
      * Dependencies for {@link MdnsAdvertiser}, useful for testing.
      */
@@ -71,11 +77,32 @@
         public MdnsInterfaceAdvertiser makeAdvertiser(@NonNull MdnsInterfaceSocket socket,
                 @NonNull List<LinkAddress> initialAddresses,
                 @NonNull Looper looper, @NonNull byte[] packetCreationBuffer,
-                @NonNull MdnsInterfaceAdvertiser.Callback cb) {
+                @NonNull MdnsInterfaceAdvertiser.Callback cb,
+                @NonNull String[] deviceHostName) {
             // Note NetworkInterface is final and not mockable
             final String logTag = socket.getInterface().getName();
             return new MdnsInterfaceAdvertiser(logTag, socket, initialAddresses, looper,
-                    packetCreationBuffer, cb);
+                    packetCreationBuffer, cb, deviceHostName);
+        }
+
+        /**
+         * Generates a unique hostname to be used by the device.
+         */
+        @NonNull
+        public String[] generateHostname() {
+            // Generate a very-probably-unique hostname. This allows minimizing possible conflicts
+            // to the point that probing for it is no longer necessary (as per RFC6762 8.1 last
+            // paragraph), and does not leak more information than what could already be obtained by
+            // looking at the mDNS packets source address.
+            // This differs from historical behavior that just used "Android.local" for many
+            // devices, creating a lot of conflicts.
+            // Having a different hostname per interface is an acceptable option as per RFC6762 14.
+            // This hostname will change every time the interface is reconnected, so this does not
+            // allow tracking the device.
+            // TODO: consider deriving a hostname from other sources, such as the IPv6 addresses
+            // (reusing the same privacy-protecting mechanics).
+            return new String[] {
+                    "Android_" + UUID.randomUUID().toString().replace("-", ""), LOCAL_TLD };
         }
     }
 
@@ -260,7 +287,7 @@
             MdnsInterfaceAdvertiser advertiser = mAllAdvertisers.get(socket);
             if (advertiser == null) {
                 advertiser = mDeps.makeAdvertiser(socket, addresses, mLooper, mPacketCreationBuffer,
-                        mInterfaceAdvertiserCb);
+                        mInterfaceAdvertiserCb, mDeviceHostName);
                 mAllAdvertisers.put(socket, advertiser);
                 advertiser.start();
             }
@@ -389,6 +416,7 @@
         mCb = cb;
         mSocketProvider = socketProvider;
         mDeps = deps;
+        mDeviceHostName = deps.generateHostname();
     }
 
     private void checkThread() {
@@ -453,6 +481,10 @@
             advertiser.removeService(id);
         }
         mRegistrations.remove(id);
+        // Regenerates host name when registrations removed.
+        if (mRegistrations.size() == 0) {
+            mDeviceHostName = mDeps.generateHostname();
+        }
     }
 
     private static <K, V> boolean any(@NonNull ArrayMap<K, V> map,
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsDiscoveryManager.java b/service-t/src/com/android/server/connectivity/mdns/MdnsDiscoveryManager.java
index cc6b98b..fb8af8d 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsDiscoveryManager.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsDiscoveryManager.java
@@ -16,19 +16,25 @@
 
 package com.android.server.connectivity.mdns;
 
+import static com.android.server.connectivity.mdns.MdnsSocketProvider.isNetworkMatched;
+
 import android.Manifest.permission;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
+import android.net.Network;
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.Log;
+import android.util.Pair;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.connectivity.mdns.util.MdnsLogger;
 
 import java.io.IOException;
-import java.util.Arrays;
-import java.util.Map;
+import java.util.ArrayList;
+import java.util.List;
 
 /**
  * This class keeps tracking the set of registered {@link MdnsServiceBrowserListener} instances, and
@@ -42,12 +48,62 @@
     private final ExecutorProvider executorProvider;
     private final MdnsSocketClientBase socketClient;
 
-    private final Map<String, MdnsServiceTypeClient> serviceTypeClients = new ArrayMap<>();
+    @GuardedBy("this")
+    @NonNull private final PerNetworkServiceTypeClients perNetworkServiceTypeClients;
+
+    private static class PerNetworkServiceTypeClients {
+        private final ArrayMap<Pair<String, Network>, MdnsServiceTypeClient> clients =
+                new ArrayMap<>();
+
+        public void put(@NonNull String serviceType, @Nullable Network network,
+                @NonNull MdnsServiceTypeClient client) {
+            final Pair<String, Network> perNetworkServiceType = new Pair<>(serviceType, network);
+            clients.put(perNetworkServiceType, client);
+        }
+
+        @Nullable
+        public MdnsServiceTypeClient get(@NonNull String serviceType, @Nullable Network network) {
+            final Pair<String, Network> perNetworkServiceType = new Pair<>(serviceType, network);
+            return clients.getOrDefault(perNetworkServiceType, null);
+        }
+
+        public List<MdnsServiceTypeClient> getByServiceType(@NonNull String serviceType) {
+            final List<MdnsServiceTypeClient> list = new ArrayList<>();
+            for (int i = 0; i < clients.size(); i++) {
+                final Pair<String, Network> perNetworkServiceType = clients.keyAt(i);
+                if (serviceType.equals(perNetworkServiceType.first)) {
+                    list.add(clients.valueAt(i));
+                }
+            }
+            return list;
+        }
+
+        public List<MdnsServiceTypeClient> getByMatchingNetwork(@Nullable Network network) {
+            final List<MdnsServiceTypeClient> list = new ArrayList<>();
+            for (int i = 0; i < clients.size(); i++) {
+                final Pair<String, Network> perNetworkServiceType = clients.keyAt(i);
+                if (isNetworkMatched(network, perNetworkServiceType.second)) {
+                    list.add(clients.valueAt(i));
+                }
+            }
+            return list;
+        }
+
+        public void remove(@NonNull MdnsServiceTypeClient client) {
+            final int index = clients.indexOfValue(client);
+            clients.removeAt(index);
+        }
+
+        public boolean isEmpty() {
+            return clients.isEmpty();
+        }
+    }
 
     public MdnsDiscoveryManager(@NonNull ExecutorProvider executorProvider,
             @NonNull MdnsSocketClientBase socketClient) {
         this.executorProvider = executorProvider;
         this.socketClient = socketClient;
+        perNetworkServiceTypeClients = new PerNetworkServiceTypeClients();
     }
 
     /**
@@ -67,7 +123,7 @@
         LOGGER.log(
                 "Registering listener for subtypes: %s",
                 TextUtils.join(",", searchOptions.getSubtypes()));
-        if (serviceTypeClients.isEmpty()) {
+        if (perNetworkServiceTypeClients.isEmpty()) {
             // First listener. Starts the socket client.
             try {
                 socketClient.startDiscovery();
@@ -77,16 +133,18 @@
             }
         }
         // Request the network for discovery.
-        socketClient.notifyNetworkRequested(listener, searchOptions.getNetwork());
-
-        // All listeners of the same service types shares the same MdnsServiceTypeClient.
-        MdnsServiceTypeClient serviceTypeClient = serviceTypeClients.get(serviceType);
-        if (serviceTypeClient == null) {
-            serviceTypeClient = createServiceTypeClient(serviceType);
-            serviceTypeClients.put(serviceType, serviceTypeClient);
-        }
-        // TODO(b/264634275): Wait for a socket to be created before sending packets.
-        serviceTypeClient.startSendAndReceive(listener, searchOptions);
+        socketClient.notifyNetworkRequested(listener, searchOptions.getNetwork(), network -> {
+            synchronized (this) {
+                // All listeners of the same service types shares the same MdnsServiceTypeClient.
+                MdnsServiceTypeClient serviceTypeClient =
+                        perNetworkServiceTypeClients.get(serviceType, network);
+                if (serviceTypeClient == null) {
+                    serviceTypeClient = createServiceTypeClient(serviceType, network);
+                    perNetworkServiceTypeClients.put(serviceType, network, serviceTypeClient);
+                }
+                serviceTypeClient.startSendAndReceive(listener, searchOptions);
+            }
+        });
     }
 
     /**
@@ -101,17 +159,21 @@
             @NonNull String serviceType, @NonNull MdnsServiceBrowserListener listener) {
         LOGGER.log("Unregistering listener for service type: %s", serviceType);
         if (DBG) Log.d(TAG, "Unregistering listener for serviceType:" + serviceType);
-        MdnsServiceTypeClient serviceTypeClient = serviceTypeClients.get(serviceType);
-        if (serviceTypeClient == null) {
+        final List<MdnsServiceTypeClient> serviceTypeClients =
+                perNetworkServiceTypeClients.getByServiceType(serviceType);
+        if (serviceTypeClients.isEmpty()) {
             return;
         }
-        if (serviceTypeClient.stopSendAndReceive(listener)) {
-            // No listener is registered for the service type anymore, remove it from the list of
-            // the service type clients.
-            serviceTypeClients.remove(serviceType);
-            if (serviceTypeClients.isEmpty()) {
-                // No discovery request. Stops the socket client.
-                socketClient.stopDiscovery();
+        for (int i = 0; i < serviceTypeClients.size(); i++) {
+            final MdnsServiceTypeClient serviceTypeClient = serviceTypeClients.get(i);
+            if (serviceTypeClient.stopSendAndReceive(listener)) {
+                // No listener is registered for the service type anymore, remove it from the list
+                // of the service type clients.
+                perNetworkServiceTypeClients.remove(serviceTypeClient);
+                if (perNetworkServiceTypeClients.isEmpty()) {
+                    // No discovery request. Stops the socket client.
+                    socketClient.stopDiscovery();
+                }
             }
         }
         // Unrequested the network.
@@ -119,36 +181,28 @@
     }
 
     @Override
-    public synchronized void onResponseReceived(@NonNull MdnsResponse response) {
-        String[] name =
-                response.getPointerRecords().isEmpty()
-                        ? null
-                        : response.getPointerRecords().get(0).getName();
-        if (name != null) {
-            for (MdnsServiceTypeClient serviceTypeClient : serviceTypeClients.values()) {
-                String[] serviceType = serviceTypeClient.getServiceTypeLabels();
-                if ((Arrays.equals(name, serviceType)
-                        || ((name.length == (serviceType.length + 2))
-                        && name[1].equals(MdnsConstants.SUBTYPE_LABEL)
-                        && MdnsRecord.labelsAreSuffix(serviceType, name)))) {
-                    serviceTypeClient.processResponse(response);
-                    return;
-                }
-            }
+    public synchronized void onResponseReceived(@NonNull MdnsPacket packet,
+            int interfaceIndex, Network network) {
+        for (MdnsServiceTypeClient serviceTypeClient
+                : perNetworkServiceTypeClients.getByMatchingNetwork(network)) {
+            serviceTypeClient.processResponse(packet, interfaceIndex, network);
         }
     }
 
     @Override
-    public synchronized void onFailedToParseMdnsResponse(int receivedPacketNumber, int errorCode) {
-        for (MdnsServiceTypeClient serviceTypeClient : serviceTypeClients.values()) {
+    public synchronized void onFailedToParseMdnsResponse(int receivedPacketNumber, int errorCode,
+            Network network) {
+        for (MdnsServiceTypeClient serviceTypeClient
+                : perNetworkServiceTypeClients.getByMatchingNetwork(network)) {
             serviceTypeClient.onFailedToParseMdnsResponse(receivedPacketNumber, errorCode);
         }
     }
 
     @VisibleForTesting
-    MdnsServiceTypeClient createServiceTypeClient(@NonNull String serviceType) {
+    MdnsServiceTypeClient createServiceTypeClient(@NonNull String serviceType,
+            @Nullable Network network) {
         return new MdnsServiceTypeClient(
                 serviceType, socketClient,
-                executorProvider.newServiceTypeClientSchedulerExecutor());
+                executorProvider.newServiceTypeClientSchedulerExecutor(), network);
     }
 }
\ No newline at end of file
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiser.java b/service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiser.java
index c616e01..79cddce 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiser.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiser.java
@@ -141,8 +141,9 @@
     public static class Dependencies {
         /** @see MdnsRecordRepository */
         @NonNull
-        public MdnsRecordRepository makeRecordRepository(@NonNull Looper looper) {
-            return new MdnsRecordRepository(looper);
+        public MdnsRecordRepository makeRecordRepository(@NonNull Looper looper,
+                @NonNull String[] deviceHostName) {
+            return new MdnsRecordRepository(looper, deviceHostName);
         }
 
         /** @see MdnsReplySender */
@@ -169,17 +170,18 @@
 
     public MdnsInterfaceAdvertiser(@NonNull String logTag,
             @NonNull MdnsInterfaceSocket socket, @NonNull List<LinkAddress> initialAddresses,
-            @NonNull Looper looper, @NonNull byte[] packetCreationBuffer, @NonNull Callback cb) {
+            @NonNull Looper looper, @NonNull byte[] packetCreationBuffer, @NonNull Callback cb,
+            @NonNull String[] deviceHostName) {
         this(logTag, socket, initialAddresses, looper, packetCreationBuffer, cb,
-                new Dependencies());
+                new Dependencies(), deviceHostName);
     }
 
     public MdnsInterfaceAdvertiser(@NonNull String logTag,
             @NonNull MdnsInterfaceSocket socket, @NonNull List<LinkAddress> initialAddresses,
             @NonNull Looper looper, @NonNull byte[] packetCreationBuffer, @NonNull Callback cb,
-            @NonNull Dependencies deps) {
+            @NonNull Dependencies deps, @NonNull String[] deviceHostName) {
         mTag = MdnsInterfaceAdvertiser.class.getSimpleName() + "/" + logTag;
-        mRecordRepository = deps.makeRecordRepository(looper);
+        mRecordRepository = deps.makeRecordRepository(looper, deviceHostName);
         mRecordRepository.updateAddresses(initialAddresses);
         mSocket = socket;
         mCb = cb;
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClient.java b/service-t/src/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClient.java
index d959065..5254342 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClient.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClient.java
@@ -33,7 +33,6 @@
 import java.net.Inet4Address;
 import java.net.Inet6Address;
 import java.net.InetSocketAddress;
-import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 
@@ -48,7 +47,6 @@
 
     @NonNull private final Handler mHandler;
     @NonNull private final MdnsSocketProvider mSocketProvider;
-    @NonNull private final MdnsResponseDecoder mResponseDecoder;
 
     private final Map<MdnsServiceBrowserListener, InterfaceSocketCallback> mRequestedNetworks =
             new ArrayMap<>();
@@ -62,11 +60,15 @@
             @NonNull MdnsSocketProvider provider) {
         mHandler = new Handler(looper);
         mSocketProvider = provider;
-        mResponseDecoder = new MdnsResponseDecoder(
-                new MdnsResponseDecoder.Clock(), null /* serviceType */);
     }
 
     private class InterfaceSocketCallback implements MdnsSocketProvider.SocketCallback {
+        private final SocketCreationCallback mSocketCreationCallback;
+
+        InterfaceSocketCallback(SocketCreationCallback socketCreationCallback) {
+            mSocketCreationCallback = socketCreationCallback;
+        }
+
         @Override
         public void onSocketCreated(@NonNull Network network,
                 @NonNull MdnsInterfaceSocket socket, @NonNull List<LinkAddress> addresses) {
@@ -80,6 +82,7 @@
             }
             socket.addPacketHandler(handler);
             mActiveNetworkSockets.put(socket, network);
+            mSocketCreationCallback.onSocketCreated(network);
         }
 
         @Override
@@ -118,10 +121,11 @@
      * @param listener the listener for discovery.
      * @param network the target network for discovery. Null means discovery on all possible
      *                interfaces.
+     * @param socketCreationCallback the callback to notify socket creation.
      */
     @Override
     public void notifyNetworkRequested(@NonNull MdnsServiceBrowserListener listener,
-            @Nullable Network network) {
+            @Nullable Network network, @NonNull SocketCreationCallback socketCreationCallback) {
         ensureRunningOnHandlerThread(mHandler);
         InterfaceSocketCallback callback = mRequestedNetworks.get(listener);
         if (callback != null) {
@@ -129,7 +133,7 @@
         }
 
         if (DBG) Log.d(TAG, "notifyNetworkRequested: network=" + network);
-        callback = new InterfaceSocketCallback();
+        callback = new InterfaceSocketCallback(socketCreationCallback);
         mRequestedNetworks.put(listener, callback);
         mSocketProvider.requestSocket(network, callback);
     }
@@ -170,19 +174,21 @@
             @NonNull Network network) {
         int packetNumber = ++mReceivedPacketNumber;
 
-        final List<MdnsResponse> responses = new ArrayList<>();
-        final int errorCode = mResponseDecoder.decode(
-                recvbuf, length, responses, interfaceIndex, network);
-        if (errorCode == MdnsResponseDecoder.SUCCESS) {
-            for (MdnsResponse response : responses) {
+        final MdnsPacket response;
+        try {
+            response = MdnsResponseDecoder.parseResponse(recvbuf, length);
+        } catch (MdnsPacket.ParseException e) {
+            if (e.code != MdnsResponseErrorCode.ERROR_NOT_RESPONSE_MESSAGE) {
+                Log.e(TAG, e.getMessage(), e);
                 if (mCallback != null) {
-                    mCallback.onResponseReceived(response);
+                    mCallback.onFailedToParseMdnsResponse(packetNumber, e.code, network);
                 }
             }
-        } else if (errorCode != MdnsResponseErrorCode.ERROR_NOT_RESPONSE_MESSAGE) {
-            if (mCallback != null) {
-                mCallback.onFailedToParseMdnsResponse(packetNumber, errorCode);
-            }
+            return;
+        }
+
+        if (mCallback != null) {
+            mCallback.onResponseReceived(response, interfaceIndex, network);
         }
     }
 
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsRecordRepository.java b/service-t/src/com/android/server/connectivity/mdns/MdnsRecordRepository.java
index e975ab4..1329172 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsRecordRepository.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsRecordRepository.java
@@ -47,7 +47,6 @@
 import java.util.Random;
 import java.util.Set;
 import java.util.TreeMap;
-import java.util.UUID;
 import java.util.concurrent.TimeUnit;
 
 /**
@@ -90,15 +89,16 @@
     @NonNull
     private final Looper mLooper;
     @NonNull
-    private String[] mDeviceHostname;
+    private final String[] mDeviceHostname;
 
-    public MdnsRecordRepository(@NonNull Looper looper) {
-        this(looper, new Dependencies());
+    public MdnsRecordRepository(@NonNull Looper looper, @NonNull String[] deviceHostname) {
+        this(looper, new Dependencies(), deviceHostname);
     }
 
     @VisibleForTesting
-    public MdnsRecordRepository(@NonNull Looper looper, @NonNull Dependencies deps) {
-        mDeviceHostname = deps.getHostname();
+    public MdnsRecordRepository(@NonNull Looper looper, @NonNull Dependencies deps,
+            @NonNull String[] deviceHostname) {
+        mDeviceHostname = deviceHostname;
         mLooper = looper;
     }
 
@@ -107,25 +107,6 @@
      */
     @VisibleForTesting
     public static class Dependencies {
-        /**
-         * Get a unique hostname to be used by the device.
-         */
-        @NonNull
-        public String[] getHostname() {
-            // Generate a very-probably-unique hostname. This allows minimizing possible conflicts
-            // to the point that probing for it is no longer necessary (as per RFC6762 8.1 last
-            // paragraph), and does not leak more information than what could already be obtained by
-            // looking at the mDNS packets source address.
-            // This differs from historical behavior that just used "Android.local" for many
-            // devices, creating a lot of conflicts.
-            // Having a different hostname per interface is an acceptable option as per RFC6762 14.
-            // This hostname will change every time the interface is reconnected, so this does not
-            // allow tracking the device.
-            // TODO: consider deriving a hostname from other sources, such as the IPv6 addresses
-            // (reusing the same privacy-protecting mechanics).
-            return new String[] {
-                    "Android_" + UUID.randomUUID().toString().replace("-", ""), LOCAL_TLD };
-        }
 
         /**
          * @see NetworkInterface#getInetAddresses().
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 3a41978..be2555b 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsResponse.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsResponse.java
@@ -16,16 +16,20 @@
 
 package com.android.server.connectivity.mdns;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.net.Network;
 
 import com.android.internal.annotations.VisibleForTesting;
 
 import java.io.IOException;
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Objects;
 
 /** An mDNS response. */
 public class MdnsResponse {
@@ -33,42 +37,72 @@
     private final List<MdnsPointerRecord> pointerRecords;
     private MdnsServiceRecord serviceRecord;
     private MdnsTextRecord textRecord;
-    private MdnsInetAddressRecord inet4AddressRecord;
-    private MdnsInetAddressRecord inet6AddressRecord;
+    @NonNull private List<MdnsInetAddressRecord> inet4AddressRecords;
+    @NonNull private List<MdnsInetAddressRecord> inet6AddressRecords;
     private long lastUpdateTime;
     private final int interfaceIndex;
     @Nullable private final Network network;
+    @NonNull private final String[] serviceName;
 
     /** Constructs a new, empty response. */
-    public MdnsResponse(long now, int interfaceIndex, @Nullable Network network) {
+    public MdnsResponse(long now, @NonNull String[] serviceName, int interfaceIndex,
+            @Nullable Network network) {
         lastUpdateTime = now;
         records = new LinkedList<>();
         pointerRecords = new LinkedList<>();
+        inet4AddressRecords = new ArrayList<>();
+        inet6AddressRecords = new ArrayList<>();
         this.interfaceIndex = interfaceIndex;
         this.network = network;
+        this.serviceName = serviceName;
     }
 
-    // This generic typed helper compares records for equality.
-    // Returns True if records are the same.
-    private <T> boolean recordsAreSame(T a, T b) {
-        return ((a == null) && (b == null)) || ((a != null) && (b != null) && a.equals(b));
+    public MdnsResponse(@NonNull MdnsResponse base) {
+        records = new ArrayList<>(base.records);
+        pointerRecords = new ArrayList<>(base.pointerRecords);
+        serviceRecord = base.serviceRecord;
+        textRecord = base.textRecord;
+        inet4AddressRecords = new ArrayList<>(base.inet4AddressRecords);
+        inet6AddressRecords = new ArrayList<>(base.inet6AddressRecords);
+        lastUpdateTime = base.lastUpdateTime;
+        serviceName = base.serviceName;
+        interfaceIndex = base.interfaceIndex;
+        network = base.network;
+    }
+
+    /**
+     * Compare records for equality, including their TTL.
+     *
+     * MdnsRecord#equals ignores TTL and receiptTimeMillis, but methods in this class need to update
+     * records when the TTL changes (especially for goodbye announcements).
+     */
+    private boolean recordsAreSame(MdnsRecord a, MdnsRecord b) {
+        if (!Objects.equals(a, b)) return false;
+        return a == null || a.getTtl() == b.getTtl();
     }
 
     /**
      * Adds a pointer record.
      *
      * @return <code>true</code> if the record was added, or <code>false</code> if a matching
-     * pointer
-     * record is already present in the response.
+     * pointer record is already present in the response with the same TTL.
      */
     public synchronized boolean addPointerRecord(MdnsPointerRecord pointerRecord) {
-        if (!pointerRecords.contains(pointerRecord)) {
-            pointerRecords.add(pointerRecord);
-            records.add(pointerRecord);
-            return true;
+        if (!Arrays.equals(serviceName, pointerRecord.getPointer())) {
+            throw new IllegalArgumentException(
+                    "Pointer records for different service names cannot be added");
         }
-
-        return false;
+        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;
     }
 
     /** Gets the pointer records. */
@@ -170,44 +204,60 @@
         return textRecord != null;
     }
 
-    /** Sets the IPv4 address record. */
-    public synchronized boolean setInet4AddressRecord(
-            @Nullable MdnsInetAddressRecord newInet4AddressRecord) {
-        if (recordsAreSame(this.inet4AddressRecord, newInet4AddressRecord)) {
-            return false;
+    /** 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);
         }
-        if (this.inet4AddressRecord != null) {
-            records.remove(this.inet4AddressRecord);
-        }
-        if (newInet4AddressRecord != null && newInet4AddressRecord.getInet4Address() != null) {
-            this.inet4AddressRecord = newInet4AddressRecord;
-            records.add(this.inet4AddressRecord);
-        }
+        inet4AddressRecords.add(newInet4AddressRecord);
+        records.add(newInet4AddressRecord);
         return true;
     }
 
-    /** Gets the IPv4 address record. */
+    /** Gets the IPv4 address records. */
+    @NonNull
+    public synchronized List<MdnsInetAddressRecord> getInet4AddressRecords() {
+        return Collections.unmodifiableList(inet4AddressRecords);
+    }
+
+    /** Return the first IPv4 address record or null if no record. */
+    @Nullable
     public synchronized MdnsInetAddressRecord getInet4AddressRecord() {
-        return inet4AddressRecord;
+        return inet4AddressRecords.isEmpty() ? null : inet4AddressRecords.get(0);
     }
 
+    /** Check whether response has IPv4 address record */
     public synchronized boolean hasInet4AddressRecord() {
-        return inet4AddressRecord != null;
+        return !inet4AddressRecords.isEmpty();
     }
 
-    /** Sets the IPv6 address record. */
-    public synchronized boolean setInet6AddressRecord(
-            @Nullable MdnsInetAddressRecord newInet6AddressRecord) {
-        if (recordsAreSame(this.inet6AddressRecord, newInet6AddressRecord)) {
-            return false;
+    /** Clear all IPv4 address records */
+    synchronized void clearInet4AddressRecords() {
+        for (MdnsInetAddressRecord record : inet4AddressRecords) {
+            records.remove(record);
         }
-        if (this.inet6AddressRecord != null) {
-            records.remove(this.inet6AddressRecord);
+        inet4AddressRecords.clear();
+    }
+
+    /** 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);
         }
-        if (newInet6AddressRecord != null && newInet6AddressRecord.getInet6Address() != null) {
-            this.inet6AddressRecord = newInet6AddressRecord;
-            records.add(this.inet6AddressRecord);
-        }
+        inet6AddressRecords.add(newInet6AddressRecord);
+        records.add(newInet6AddressRecord);
         return true;
     }
 
@@ -227,13 +277,28 @@
         return network;
     }
 
-    /** Gets the IPv6 address record. */
-    public synchronized MdnsInetAddressRecord getInet6AddressRecord() {
-        return inet6AddressRecord;
+    /** Gets all IPv6 address records. */
+    public synchronized List<MdnsInetAddressRecord> getInet6AddressRecords() {
+        return Collections.unmodifiableList(inet6AddressRecords);
     }
 
+    /** Return the first IPv6 address record or null if no record. */
+    @Nullable
+    public synchronized MdnsInetAddressRecord getInet6AddressRecord() {
+        return inet6AddressRecords.isEmpty() ? null : inet6AddressRecords.get(0);
+    }
+
+    /** Check whether response has IPv6 address record */
     public synchronized boolean hasInet6AddressRecord() {
-        return inet6AddressRecord != null;
+        return !inet6AddressRecords.isEmpty();
+    }
+
+    /** Clear all IPv6 address records */
+    synchronized void clearInet6AddressRecords() {
+        for (MdnsInetAddressRecord record : inet6AddressRecords) {
+            records.remove(record);
+        }
+        inet6AddressRecords.clear();
     }
 
     /** Gets all of the records. */
@@ -242,101 +307,58 @@
     }
 
     /**
-     * Merges any records that are present in another response into this one.
+     * Drop address records if they are for a hostname that does not match the service record.
      *
-     * @return <code>true</code> if any records were added or updated.
+     * @return True if the records were dropped.
      */
-    public synchronized boolean mergeRecordsFrom(MdnsResponse other) {
-        lastUpdateTime = other.lastUpdateTime;
+    public synchronized boolean dropUnmatchedAddressRecords() {
+        if (this.serviceRecord == null) return false;
+        boolean dropAddressRecords = false;
 
-        boolean updated = false;
-
-        List<MdnsPointerRecord> pointerRecords = other.getPointerRecords();
-        if (pointerRecords != null) {
-            for (MdnsPointerRecord pointerRecord : pointerRecords) {
-                if (addPointerRecord(pointerRecord)) {
-                    updated = true;
-                }
+        for (MdnsInetAddressRecord inetAddressRecord : getInet4AddressRecords()) {
+            if (!Arrays.equals(
+                    this.serviceRecord.getServiceHost(), inetAddressRecord.getName())) {
+                dropAddressRecords = true;
+            }
+        }
+        for (MdnsInetAddressRecord inetAddressRecord : getInet6AddressRecords()) {
+            if (!Arrays.equals(
+                    this.serviceRecord.getServiceHost(), inetAddressRecord.getName())) {
+                dropAddressRecords = true;
             }
         }
 
-        MdnsServiceRecord serviceRecord = other.getServiceRecord();
-        if (serviceRecord != null) {
-            if (setServiceRecord(serviceRecord)) {
-                updated = true;
-            }
+        if (dropAddressRecords) {
+            clearInet4AddressRecords();
+            clearInet6AddressRecords();
+            return true;
         }
-
-        MdnsTextRecord textRecord = other.getTextRecord();
-        if (textRecord != null) {
-            if (setTextRecord(textRecord)) {
-                updated = true;
-            }
-        }
-
-        MdnsInetAddressRecord otherInet4AddressRecord = other.getInet4AddressRecord();
-        if (otherInet4AddressRecord != null && otherInet4AddressRecord.getInet4Address() != null) {
-            if (setInet4AddressRecord(otherInet4AddressRecord)) {
-                updated = true;
-            }
-        }
-
-        MdnsInetAddressRecord otherInet6AddressRecord = other.getInet6AddressRecord();
-        if (otherInet6AddressRecord != null && otherInet6AddressRecord.getInet6Address() != null) {
-            if (setInet6AddressRecord(otherInet6AddressRecord)) {
-                updated = true;
-            }
-        }
-
-        // If the hostname in the service record no longer matches the hostname in either of the
-        // address records, then drop the address records.
-        if (this.serviceRecord != null) {
-            boolean dropAddressRecords = false;
-
-            if (this.inet4AddressRecord != null) {
-                if (!Arrays.equals(
-                        this.serviceRecord.getServiceHost(), this.inet4AddressRecord.getName())) {
-                    dropAddressRecords = true;
-                }
-            }
-            if (this.inet6AddressRecord != null) {
-                if (!Arrays.equals(
-                        this.serviceRecord.getServiceHost(), this.inet6AddressRecord.getName())) {
-                    dropAddressRecords = true;
-                }
-            }
-
-            if (dropAddressRecords) {
-                setInet4AddressRecord(null);
-                setInet6AddressRecord(null);
-                updated = true;
-            }
-        }
-
-        return updated;
+        return false;
     }
 
     /**
-     * Tests if the response is complete. A response is considered complete if it contains PTR, SRV,
-     * TXT, and A (for IPv4) or AAAA (for IPv6) records.
+     * Tests if the response is complete. A response is considered complete if it contains SRV,
+     * TXT, and A (for IPv4) or AAAA (for IPv6) records. The service type->name mapping is always
+     * known when constructing a MdnsResponse, so this may return true when there is no PTR record.
      */
     public synchronized boolean isComplete() {
-        return !pointerRecords.isEmpty()
-                && (serviceRecord != null)
+        return (serviceRecord != null)
                 && (textRecord != null)
-                && (inet4AddressRecord != null || inet6AddressRecord != null);
+                && (!inet4AddressRecords.isEmpty() || !inet6AddressRecords.isEmpty());
     }
 
     /**
      * Returns the key for this response. The key uniquely identifies the response by its service
      * name.
      */
-    public synchronized String getServiceInstanceName() {
-        if (pointerRecords.isEmpty()) {
-            return null;
-        }
-        String[] pointers = pointerRecords.get(0).getPointer();
-        return ((pointers != null) && (pointers.length > 0)) ? pointers[0] : null;
+    @Nullable
+    public String getServiceInstanceName() {
+        return serviceName.length > 0 ? serviceName[0] : null;
+    }
+
+    @NonNull
+    public String[] getServiceName() {
+        return serviceName;
     }
 
     /**
@@ -388,13 +410,13 @@
             ++count;
         }
 
-        if (inet4AddressRecord != null) {
-            inet4AddressRecord.write(writer, now);
+        for (MdnsInetAddressRecord inetAddressRecord : inet4AddressRecords) {
+            inetAddressRecord.write(writer, now);
             ++count;
         }
 
-        if (inet6AddressRecord != null) {
-            inet6AddressRecord.write(writer, now);
+        for (MdnsInetAddressRecord inetAddressRecord : inet6AddressRecords) {
+            inetAddressRecord.write(writer, now);
             ++count;
         }
 
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 82da2e4..0151202 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsResponseDecoder.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsResponseDecoder.java
@@ -20,18 +20,18 @@
 import android.annotation.Nullable;
 import android.net.Network;
 import android.os.SystemClock;
+import android.util.ArraySet;
 
 import com.android.server.connectivity.mdns.util.MdnsLogger;
 
 import java.io.EOFException;
-import java.net.DatagramPacket;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.List;
 
 /** A class that decodes mDNS responses from UDP packets. */
 public class MdnsResponseDecoder {
-
     public static final int SUCCESS = 0;
     private static final String TAG = "MdnsResponseDecoder";
     private static final MdnsLogger LOGGER = new MdnsLogger(TAG);
@@ -50,14 +50,8 @@
             List<MdnsResponse> responses, String[] pointer) {
         if (responses != null) {
             for (MdnsResponse response : responses) {
-                List<MdnsPointerRecord> pointerRecords = response.getPointerRecords();
-                if (pointerRecords == null) {
-                    continue;
-                }
-                for (MdnsPointerRecord pointerRecord : pointerRecords) {
-                    if (Arrays.equals(pointerRecord.getPointer(), pointer)) {
-                        return response;
-                    }
+                if (Arrays.equals(response.getServiceName(), pointer)) {
+                    return response;
                 }
             }
         }
@@ -82,34 +76,16 @@
 
     /**
      * Decodes all mDNS responses for the desired service type from a packet. The class does not
-     * check
-     * the responses for completeness; the caller should do that.
-     *
-     * @param packet The packet to read from.
-     * @param interfaceIndex the network interface index (or {@link
-     *     MdnsSocket#INTERFACE_INDEX_UNSPECIFIED} if not known) at which the packet was received
-     * @param network the network at which the packet was received, or null if it is unknown.
-     * @return A list of mDNS responses, or null if the packet contained no appropriate responses.
-     */
-    public int decode(@NonNull DatagramPacket packet, @NonNull List<MdnsResponse> responses,
-            int interfaceIndex, @Nullable Network network) {
-        return decode(packet.getData(), packet.getLength(), responses, interfaceIndex, network);
-    }
-
-    /**
-     * Decodes all mDNS responses for the desired service type from a packet. The class does not
-     * check
-     * the responses for completeness; the caller should do that.
+     * check the responses for completeness; the caller should do that.
      *
      * @param recvbuf The received data buffer to read from.
      * @param length The length of received data buffer.
-     * @param interfaceIndex the network interface index (or {@link
-     *     MdnsSocket#INTERFACE_INDEX_UNSPECIFIED} if not known) at which the packet was received
-     * @param network the network at which the packet was received, or null if it is unknown.
-     * @return A list of mDNS responses, or null if the packet contained no appropriate responses.
+     * @return A decoded {@link MdnsPacket}.
+     * @throws MdnsPacket.ParseException if a response packet could not be parsed.
      */
-    public int decode(@NonNull byte[] recvbuf, int length, @NonNull List<MdnsResponse> responses,
-            int interfaceIndex, @Nullable Network network) {
+    @NonNull
+    public static MdnsPacket parseResponse(@NonNull byte[] recvbuf, int length)
+            throws MdnsPacket.ParseException {
         MdnsPacketReader reader = new MdnsPacketReader(recvbuf, length);
 
         final MdnsPacket mdnsPacket;
@@ -117,21 +93,37 @@
             reader.readUInt16(); // transaction ID (not used)
             int flags = reader.readUInt16();
             if ((flags & MdnsConstants.FLAGS_RESPONSE_MASK) != MdnsConstants.FLAGS_RESPONSE) {
-                return MdnsResponseErrorCode.ERROR_NOT_RESPONSE_MESSAGE;
+                throw new MdnsPacket.ParseException(
+                        MdnsResponseErrorCode.ERROR_NOT_RESPONSE_MESSAGE, "Not a response", null);
             }
 
             mdnsPacket = MdnsPacket.parseRecordsSection(reader, flags);
             if (mdnsPacket.answers.size() < 1) {
-                return MdnsResponseErrorCode.ERROR_NO_ANSWERS;
+                throw new MdnsPacket.ParseException(
+                        MdnsResponseErrorCode.ERROR_NO_ANSWERS, "Response has no answers",
+                        null);
             }
+            return mdnsPacket;
         } catch (EOFException e) {
-            LOGGER.e("Reached the end of the mDNS response unexpectedly.", e);
-            return MdnsResponseErrorCode.ERROR_END_OF_FILE;
-        } catch (MdnsPacket.ParseException e) {
-            LOGGER.e(e.getMessage(), e);
-            return e.code;
+            throw new MdnsPacket.ParseException(MdnsResponseErrorCode.ERROR_END_OF_FILE,
+                    "Reached the end of the mDNS response unexpectedly.", e);
         }
+    }
 
+    /**
+     * Augments a list of {@link MdnsResponse} with records from a packet. The class does not check
+     * the resulting responses for completeness; the caller should do that.
+     *
+     * @param mdnsPacket the response packet with the new records
+     * @param existingResponses list of existing responses. Will not be modified.
+     * @param interfaceIndex the network interface index (or
+     * {@link MdnsSocket#INTERFACE_INDEX_UNSPECIFIED} if not known) at which the packet was received
+     * @param network the network at which the packet was received, or null if it is unknown.
+     * @return The set of response instances that were modified or newly added.
+     */
+    public ArraySet<MdnsResponse> augmentResponses(@NonNull MdnsPacket mdnsPacket,
+            @NonNull Collection<MdnsResponse> existingResponses, int interfaceIndex,
+            @Nullable Network network) {
         final ArrayList<MdnsRecord> records = new ArrayList<>(
                 mdnsPacket.questions.size() + mdnsPacket.answers.size()
                         + mdnsPacket.authorityRecords.size() + mdnsPacket.additionalRecords.size());
@@ -139,6 +131,11 @@
         records.addAll(mdnsPacket.authorityRecords);
         records.addAll(mdnsPacket.additionalRecords);
 
+        final ArraySet<MdnsResponse> modified = new ArraySet<>();
+        final ArrayList<MdnsResponse> responses = new ArrayList<>(existingResponses.size());
+        for (MdnsResponse existing : existingResponses) {
+            responses.add(new MdnsResponse(existing));
+        }
         // The response records are structured in a hierarchy, where some records reference
         // others, as follows:
         //
@@ -178,12 +175,14 @@
                     MdnsResponse response = findResponseWithPointer(responses,
                             pointerRecord.getPointer());
                     if (response == null) {
-                        response = new MdnsResponse(now, interfaceIndex, network);
+                        response = new MdnsResponse(now, pointerRecord.getPointer(), interfaceIndex,
+                                network);
                         responses.add(response);
                     }
-                    // Set interface index earlier because some responses have PTR record only.
-                    // Need to know every response is getting from which interface.
-                    response.addPointerRecord((MdnsPointerRecord) record);
+
+                    if (response.addPointerRecord((MdnsPointerRecord) record)) {
+                        modified.add(response);
+                    }
                 }
             }
         }
@@ -193,47 +192,94 @@
             if (record instanceof MdnsServiceRecord) {
                 MdnsServiceRecord serviceRecord = (MdnsServiceRecord) record;
                 MdnsResponse response = findResponseWithPointer(responses, serviceRecord.getName());
-                if (response != null) {
-                    response.setServiceRecord(serviceRecord);
+                if (response != null && response.setServiceRecord(serviceRecord)) {
+                    response.dropUnmatchedAddressRecords();
+                    modified.add(response);
                 }
             } else if (record instanceof MdnsTextRecord) {
                 MdnsTextRecord textRecord = (MdnsTextRecord) record;
                 MdnsResponse response = findResponseWithPointer(responses, textRecord.getName());
-                if (response != null) {
-                    response.setTextRecord(textRecord);
+                if (response != null && response.setTextRecord(textRecord)) {
+                    modified.add(response);
                 }
             }
         }
 
-        // Loop 3: find A and AAAA records, which reference the host name in the SRV record.
+        // Loop 3-1: find A and AAAA records and clear addresses if the cache-flush bit set, which
+        //           reference the host name in the SRV record.
+        final List<MdnsInetAddressRecord> inetRecords = new ArrayList<>();
         for (MdnsRecord record : records) {
             if (record instanceof MdnsInetAddressRecord) {
                 MdnsInetAddressRecord inetRecord = (MdnsInetAddressRecord) record;
+                inetRecords.add(inetRecord);
                 if (allowMultipleSrvRecordsPerHost) {
                     List<MdnsResponse> matchingResponses =
                             findResponsesWithHostName(responses, inetRecord.getName());
                     for (MdnsResponse response : matchingResponses) {
-                        assignInetRecord(response, inetRecord);
+                        // Per RFC6762 10.2, clear all address records if the cache-flush bit set.
+                        // 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.
+                        if (inetRecord.getCacheFlush()) {
+                            response.clearInet4AddressRecords();
+                            response.clearInet6AddressRecords();
+                        }
                     }
                 } else {
                     MdnsResponse response =
                             findResponseWithHostName(responses, inetRecord.getName());
                     if (response != null) {
-                        assignInetRecord(response, inetRecord);
+                        // Per RFC6762 10.2, clear all address records if the cache-flush bit set.
+                        // 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.
+                        if (inetRecord.getCacheFlush()) {
+                            response.clearInet4AddressRecords();
+                            response.clearInet6AddressRecords();
+                        }
                     }
                 }
             }
         }
 
-        return SUCCESS;
+        // Loop 3-2: Assign addresses, which reference the host name in the SRV record.
+        for (MdnsInetAddressRecord inetRecord : inetRecords) {
+            if (allowMultipleSrvRecordsPerHost) {
+                List<MdnsResponse> matchingResponses =
+                        findResponsesWithHostName(responses, inetRecord.getName());
+                for (MdnsResponse response : matchingResponses) {
+                    if (assignInetRecord(response, inetRecord)) {
+                        modified.add(response);
+                    }
+                }
+            } else {
+                MdnsResponse response =
+                        findResponseWithHostName(responses, inetRecord.getName());
+                if (response != null) {
+                    if (assignInetRecord(response, inetRecord)) {
+                        modified.add(response);
+                    }
+                }
+            }
+        }
+
+        return modified;
     }
 
-    private static void assignInetRecord(MdnsResponse response, MdnsInetAddressRecord inetRecord) {
+    private static boolean assignInetRecord(
+            MdnsResponse response, MdnsInetAddressRecord inetRecord) {
         if (inetRecord.getInet4Address() != null) {
-            response.setInet4AddressRecord(inetRecord);
+            return response.addInet4AddressRecord(inetRecord);
         } else if (inetRecord.getInet6Address() != null) {
-            response.setInet6AddressRecord(inetRecord);
+            return response.addInet6AddressRecord(inetRecord);
         }
+        return false;
     }
 
     private static List<MdnsResponse> findResponsesWithHostName(
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsSearchOptions.java b/service-t/src/com/android/server/connectivity/mdns/MdnsSearchOptions.java
index 583c4a9..3da6bd0 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsSearchOptions.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsSearchOptions.java
@@ -46,7 +46,8 @@
                 public MdnsSearchOptions createFromParcel(Parcel source) {
                     return new MdnsSearchOptions(source.createStringArrayList(),
                             source.readBoolean(), source.readBoolean(),
-                            source.readParcelable(null));
+                            source.readParcelable(null),
+                            source.readString());
                 }
 
                 @Override
@@ -56,6 +57,8 @@
             };
     private static MdnsSearchOptions defaultOptions;
     private final List<String> subtypes;
+    @Nullable
+    private final String resolveInstanceName;
 
     private final boolean isPassiveMode;
     private final boolean removeExpiredService;
@@ -64,7 +67,7 @@
 
     /** Parcelable constructs for a {@link MdnsSearchOptions}. */
     MdnsSearchOptions(List<String> subtypes, boolean isPassiveMode, boolean removeExpiredService,
-            @Nullable Network network) {
+            @Nullable Network network, @Nullable String resolveInstanceName) {
         this.subtypes = new ArrayList<>();
         if (subtypes != null) {
             this.subtypes.addAll(subtypes);
@@ -72,6 +75,7 @@
         this.isPassiveMode = isPassiveMode;
         this.removeExpiredService = removeExpiredService;
         mNetwork = network;
+        this.resolveInstanceName = resolveInstanceName;
     }
 
     /** Returns a {@link Builder} for {@link MdnsSearchOptions}. */
@@ -115,6 +119,15 @@
         return mNetwork;
     }
 
+    /**
+     * If non-null, queries should try to resolve all records of this specific service, rather than
+     * discovering all services.
+     */
+    @Nullable
+    public String getResolveInstanceName() {
+        return resolveInstanceName;
+    }
+
     @Override
     public int describeContents() {
         return 0;
@@ -126,6 +139,7 @@
         out.writeBoolean(isPassiveMode);
         out.writeBoolean(removeExpiredService);
         out.writeParcelable(mNetwork, 0);
+        out.writeString(resolveInstanceName);
     }
 
     /** A builder to create {@link MdnsSearchOptions}. */
@@ -134,6 +148,7 @@
         private boolean isPassiveMode = true;
         private boolean removeExpiredService;
         private Network mNetwork;
+        private String resolveInstanceName;
 
         private Builder() {
             subtypes = new ArraySet<>();
@@ -194,10 +209,22 @@
             return this;
         }
 
+        /**
+         * Set the instance name to resolve.
+         *
+         * If non-null, queries should try to resolve all records of this specific service,
+         * rather than discovering all services.
+         * @param name The instance name.
+         */
+        public Builder setResolveInstanceName(String name) {
+            resolveInstanceName = name;
+            return this;
+        }
+
         /** Builds a {@link MdnsSearchOptions} with the arguments supplied to this builder. */
         public MdnsSearchOptions build() {
             return new MdnsSearchOptions(new ArrayList<>(subtypes), isPassiveMode,
-                    removeExpiredService, mNetwork);
+                    removeExpiredService, mNetwork, resolveInstanceName);
         }
     }
 }
\ No newline at end of file
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsServiceInfo.java b/service-t/src/com/android/server/connectivity/mdns/MdnsServiceInfo.java
index 938fc3f..78df6df 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsServiceInfo.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsServiceInfo.java
@@ -31,10 +31,10 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
-import java.util.HashMap;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
+import java.util.TreeMap;
 
 /**
  * A class representing a discovered mDNS service instance.
@@ -57,8 +57,8 @@
                             source.createStringArrayList(),
                             source.createStringArray(),
                             source.readInt(),
-                            source.readString(),
-                            source.readString(),
+                            source.createStringArrayList(),
+                            source.createStringArrayList(),
                             source.createStringArrayList(),
                             source.createTypedArrayList(TextEntry.CREATOR),
                             source.readInt(),
@@ -76,10 +76,10 @@
     private final List<String> subtypes;
     private final String[] hostName;
     private final int port;
-    @Nullable
-    private final String ipv4Address;
-    @Nullable
-    private final String ipv6Address;
+    @NonNull
+    private final List<String> ipv4Addresses;
+    @NonNull
+    private final List<String> ipv6Addresses;
     final List<String> textStrings;
     @Nullable
     final List<TextEntry> textEntries;
@@ -105,8 +105,8 @@
                 subtypes,
                 hostName,
                 port,
-                ipv4Address,
-                ipv6Address,
+                List.of(ipv4Address),
+                List.of(ipv6Address),
                 textStrings,
                 /* textEntries= */ null,
                 /* interfaceIndex= */ INTERFACE_INDEX_UNSPECIFIED,
@@ -130,8 +130,8 @@
                 subtypes,
                 hostName,
                 port,
-                ipv4Address,
-                ipv6Address,
+                List.of(ipv4Address),
+                List.of(ipv6Address),
                 textStrings,
                 textEntries,
                 /* interfaceIndex= */ INTERFACE_INDEX_UNSPECIFIED,
@@ -160,8 +160,8 @@
                 subtypes,
                 hostName,
                 port,
-                ipv4Address,
-                ipv6Address,
+                List.of(ipv4Address),
+                List.of(ipv6Address),
                 textStrings,
                 textEntries,
                 interfaceIndex,
@@ -179,8 +179,8 @@
             @Nullable List<String> subtypes,
             String[] hostName,
             int port,
-            @Nullable String ipv4Address,
-            @Nullable String ipv6Address,
+            @NonNull List<String> ipv4Addresses,
+            @NonNull List<String> ipv6Addresses,
             @Nullable List<String> textStrings,
             @Nullable List<TextEntry> textEntries,
             int interfaceIndex,
@@ -193,8 +193,8 @@
         }
         this.hostName = hostName;
         this.port = port;
-        this.ipv4Address = ipv4Address;
-        this.ipv6Address = ipv6Address;
+        this.ipv4Addresses = new ArrayList<>(ipv4Addresses);
+        this.ipv6Addresses = new ArrayList<>(ipv6Addresses);
         this.textStrings = new ArrayList<>();
         if (textStrings != null) {
             this.textStrings.addAll(textStrings);
@@ -205,17 +205,14 @@
         // compatibility. We should prefer only {@code textEntries} if it's not null.
         List<TextEntry> entries =
                 (this.textEntries != null) ? this.textEntries : parseTextStrings(this.textStrings);
-        Map<String, byte[]> attributes = new HashMap<>(entries.size());
+        // The map of attributes is case-insensitive.
+        final Map<String, byte[]> attributes = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
         for (TextEntry entry : entries) {
-            String key = entry.getKey().toLowerCase(Locale.ENGLISH);
-
             // Per https://datatracker.ietf.org/doc/html/rfc6763#section-6.4, only the first entry
             // of the same key should be accepted:
             // If a client receives a TXT record containing the same key more than once, then the
             // client MUST silently ignore all but the first occurrence of that attribute.
-            if (!attributes.containsKey(key)) {
-                attributes.put(key, entry.getValue());
-            }
+            attributes.putIfAbsent(entry.getKey(), entry.getValue());
         }
         this.attributes = Collections.unmodifiableMap(attributes);
         this.interfaceIndex = interfaceIndex;
@@ -263,16 +260,41 @@
         return port;
     }
 
-    /** Returns the IPV4 address of this service instance. */
+    /** Returns the IPV4 addresses of this service instance. */
+    @NonNull
+    public List<String> getIpv4Addresses() {
+        return Collections.unmodifiableList(ipv4Addresses);
+    }
+
+    /**
+     * Returns the first IPV4 address of this service instance.
+     *
+     * @deprecated Use {@link #getIpv4Addresses()} to get the entire list of IPV4
+     * addresses for
+     * the host.
+     */
     @Nullable
+    @Deprecated
     public String getIpv4Address() {
-        return ipv4Address;
+        return ipv4Addresses.isEmpty() ? null : ipv4Addresses.get(0);
     }
 
     /** Returns the IPV6 address of this service instance. */
+    @NonNull
+    public List<String> getIpv6Addresses() {
+        return Collections.unmodifiableList(ipv6Addresses);
+    }
+
+    /**
+     * Returns the first IPV6 address of this service instance.
+     *
+     * @deprecated Use {@link #getIpv6Addresses()} to get the entire list of IPV6 addresses for
+     * the host.
+     */
     @Nullable
+    @Deprecated
     public String getIpv6Address() {
-        return ipv6Address;
+        return ipv6Addresses.isEmpty() ? null : ipv6Addresses.get(0);
     }
 
     /**
@@ -311,12 +333,12 @@
      */
     @Nullable
     public byte[] getAttributeAsBytes(@NonNull String key) {
-        return attributes.get(key.toLowerCase(Locale.ENGLISH));
+        return attributes.get(key);
     }
 
     /** Returns an immutable map of all attributes. */
     public Map<String, String> getAttributes() {
-        Map<String, String> map = new HashMap<>(attributes.size());
+        Map<String, String> map = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
         for (Map.Entry<String, byte[]> kv : attributes.entrySet()) {
             final byte[] value = kv.getValue();
             map.put(kv.getKey(), value == null ? null : new String(value, UTF_8));
@@ -336,8 +358,8 @@
         out.writeStringList(subtypes);
         out.writeStringArray(hostName);
         out.writeInt(port);
-        out.writeString(ipv4Address);
-        out.writeString(ipv6Address);
+        out.writeStringList(ipv4Addresses);
+        out.writeStringList(ipv6Addresses);
         out.writeStringList(textStrings);
         out.writeTypedList(textEntries);
         out.writeInt(interfaceIndex);
@@ -346,13 +368,16 @@
 
     @Override
     public String toString() {
-        return String.format(
-                Locale.ROOT,
-                "Name: %s, subtypes: %s, ip: %s, port: %d",
-                serviceInstanceName,
-                TextUtils.join(",", subtypes),
-                ipv4Address,
-                port);
+        return "Name: " + serviceInstanceName
+                + ", type: " + TextUtils.join(".", serviceType)
+                + ", subtypes: " + TextUtils.join(",", subtypes)
+                + ", ip: " + ipv4Addresses
+                + ", ipv6: " + ipv6Addresses
+                + ", port: " + port
+                + ", interfaceIndex: " + interfaceIndex
+                + ", network: " + network
+                + ", textStrings: " + textStrings
+                + ", textEntries: " + textEntries;
     }
 
 
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java b/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java
index d26fbdb..f87804b 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java
@@ -21,8 +21,8 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.net.Network;
-import android.os.SystemClock;
 import android.text.TextUtils;
+import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Pair;
 
@@ -33,12 +33,12 @@
 import java.net.Inet4Address;
 import java.net.Inet6Address;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
 import java.util.concurrent.Future;
 import java.util.concurrent.ScheduledExecutorService;
 
@@ -51,18 +51,24 @@
     private static final int DEFAULT_MTU = 1500;
     private static final MdnsLogger LOGGER = new MdnsLogger("MdnsServiceTypeClient");
 
+
     private final String serviceType;
     private final String[] serviceTypeLabels;
     private final MdnsSocketClientBase socketClient;
+    private final MdnsResponseDecoder responseDecoder;
     private final ScheduledExecutorService executor;
+    @Nullable private final Network network;
     private final Object lock = new Object();
-    private final Set<MdnsServiceBrowserListener> listeners = new ArraySet<>();
+    private final ArrayMap<MdnsServiceBrowserListener, MdnsSearchOptions> listeners =
+            new ArrayMap<>();
     private final Map<String, MdnsResponse> instanceNameToResponse = new HashMap<>();
     private final boolean removeServiceAfterTtlExpires =
             MdnsConfigs.removeServiceAfterTtlExpires();
     private final boolean allowSearchOptionsToRemoveExpiredService =
             MdnsConfigs.allowSearchOptionsToRemoveExpiredService();
 
+    private final MdnsResponseDecoder.Clock clock;
+
     @Nullable private MdnsSearchOptions searchOptions;
 
     // The session ID increases when startSendAndReceive() is called where we schedule a
@@ -83,11 +89,25 @@
     public MdnsServiceTypeClient(
             @NonNull String serviceType,
             @NonNull MdnsSocketClientBase socketClient,
-            @NonNull ScheduledExecutorService executor) {
+            @NonNull ScheduledExecutorService executor,
+            @Nullable Network network) {
+        this(serviceType, socketClient, executor, new MdnsResponseDecoder.Clock(), network);
+    }
+
+    @VisibleForTesting
+    public MdnsServiceTypeClient(
+            @NonNull String serviceType,
+            @NonNull MdnsSocketClientBase socketClient,
+            @NonNull ScheduledExecutorService executor,
+            @NonNull MdnsResponseDecoder.Clock clock,
+            @Nullable Network network) {
         this.serviceType = serviceType;
         this.socketClient = socketClient;
         this.executor = executor;
-        serviceTypeLabels = TextUtils.split(serviceType, "\\.");
+        this.serviceTypeLabels = TextUtils.split(serviceType, "\\.");
+        this.responseDecoder = new MdnsResponseDecoder(clock, serviceTypeLabels);
+        this.clock = clock;
+        this.network = network;
     }
 
     private static MdnsServiceInfo buildMdnsServiceInfoFromResponse(
@@ -99,15 +119,19 @@
             port = response.getServiceRecord().getServicePort();
         }
 
-        String ipv4Address = null;
-        String ipv6Address = null;
+        final List<String> ipv4Addresses = new ArrayList<>();
+        final List<String> ipv6Addresses = new ArrayList<>();
         if (response.hasInet4AddressRecord()) {
-            Inet4Address inet4Address = response.getInet4AddressRecord().getInet4Address();
-            ipv4Address = (inet4Address == null) ? null : inet4Address.getHostAddress();
+            for (MdnsInetAddressRecord inetAddressRecord : response.getInet4AddressRecords()) {
+                final Inet4Address inet4Address = inetAddressRecord.getInet4Address();
+                ipv4Addresses.add((inet4Address == null) ? null : inet4Address.getHostAddress());
+            }
         }
         if (response.hasInet6AddressRecord()) {
-            Inet6Address inet6Address = response.getInet6AddressRecord().getInet6Address();
-            ipv6Address = (inet6Address == null) ? null : inet6Address.getHostAddress();
+            for (MdnsInetAddressRecord inetAddressRecord : response.getInet6AddressRecords()) {
+                final Inet6Address inet6Address = inetAddressRecord.getInet6Address();
+                ipv6Addresses.add((inet6Address == null) ? null : inet6Address.getHostAddress());
+            }
         }
         String serviceInstanceName = response.getServiceInstanceName();
         if (serviceInstanceName == null) {
@@ -127,8 +151,8 @@
                 response.getSubtypes(),
                 hostName,
                 port,
-                ipv4Address,
-                ipv6Address,
+                ipv4Addresses,
+                ipv6Addresses,
                 textStrings,
                 textEntries,
                 response.getInterfaceIndex(),
@@ -148,7 +172,7 @@
             @NonNull MdnsSearchOptions searchOptions) {
         synchronized (lock) {
             this.searchOptions = searchOptions;
-            if (listeners.add(listener)) {
+            if (listeners.put(listener, searchOptions) == null) {
                 for (MdnsResponse existingResponse : instanceNameToResponse.values()) {
                     final MdnsServiceInfo info =
                             buildMdnsServiceInfoFromResponse(existingResponse, serviceTypeLabels);
@@ -184,7 +208,9 @@
      */
     public boolean stopSendAndReceive(@NonNull MdnsServiceBrowserListener listener) {
         synchronized (lock) {
-            listeners.remove(listener);
+            if (listeners.remove(listener) == null) {
+                return listeners.isEmpty();
+            }
             if (listeners.isEmpty() && requestTaskFuture != null) {
                 requestTaskFuture.cancel(true);
                 requestTaskFuture = null;
@@ -197,65 +223,63 @@
         return serviceTypeLabels;
     }
 
-    public synchronized void processResponse(@NonNull MdnsResponse response) {
-        if (shouldRemoveServiceAfterTtlExpires()) {
-            // Because {@link QueryTask} and {@link processResponse} are running in different
-            // threads. We need to synchronize {@link lock} to protect
-            // {@link instanceNameToResponse} won’t be modified at the same time.
-            synchronized (lock) {
-                if (response.isGoodbye()) {
-                    onGoodbyeReceived(response.getServiceInstanceName());
+    /**
+     * Process an incoming response packet.
+     */
+    public synchronized void processResponse(@NonNull MdnsPacket packet, int interfaceIndex,
+            Network network) {
+        synchronized (lock) {
+            // Augment the list of current known responses, and generated responses for resolve
+            // requests if there is no known response
+            final List<MdnsResponse> currentList = new ArrayList<>(instanceNameToResponse.values());
+            currentList.addAll(makeResponsesForResolveIfUnknown(interfaceIndex, network));
+            final ArraySet<MdnsResponse> modifiedResponses = responseDecoder.augmentResponses(
+                    packet, currentList, interfaceIndex, network);
+
+            for (MdnsResponse modified : modifiedResponses) {
+                if (modified.isGoodbye()) {
+                    onGoodbyeReceived(modified.getServiceInstanceName());
                 } else {
-                    onResponseReceived(response);
+                    onResponseModified(modified);
                 }
             }
-        } else {
-            if (response.isGoodbye()) {
-                onGoodbyeReceived(response.getServiceInstanceName());
-            } else {
-                onResponseReceived(response);
-            }
         }
     }
 
     public synchronized void onFailedToParseMdnsResponse(int receivedPacketNumber, int errorCode) {
-        for (MdnsServiceBrowserListener listener : listeners) {
-            listener.onFailedToParseMdnsResponse(receivedPacketNumber, errorCode);
+        for (int i = 0; i < listeners.size(); i++) {
+            listeners.keyAt(i).onFailedToParseMdnsResponse(receivedPacketNumber, errorCode);
         }
     }
 
-    private void onResponseReceived(@NonNull MdnsResponse response) {
-        MdnsResponse currentResponse;
-        currentResponse = instanceNameToResponse.get(response.getServiceInstanceName());
+    private void onResponseModified(@NonNull MdnsResponse response) {
+        final MdnsResponse currentResponse =
+                instanceNameToResponse.get(response.getServiceInstanceName());
 
         boolean newServiceFound = false;
-        boolean existingServiceChanged = false;
         boolean serviceBecomesComplete = false;
         if (currentResponse == null) {
             newServiceFound = true;
-            currentResponse = response;
             String serviceInstanceName = response.getServiceInstanceName();
             if (serviceInstanceName != null) {
-                instanceNameToResponse.put(serviceInstanceName, currentResponse);
+                instanceNameToResponse.put(serviceInstanceName, response);
             }
         } else {
             boolean before = currentResponse.isComplete();
-            existingServiceChanged = currentResponse.mergeRecordsFrom(response);
-            boolean after = currentResponse.isComplete();
+            instanceNameToResponse.put(response.getServiceInstanceName(), response);
+            boolean after = response.isComplete();
             serviceBecomesComplete = !before && after;
         }
-        if (!newServiceFound && !existingServiceChanged) {
-            return;
-        }
         MdnsServiceInfo serviceInfo =
-                buildMdnsServiceInfoFromResponse(currentResponse, serviceTypeLabels);
+                buildMdnsServiceInfoFromResponse(response, serviceTypeLabels);
 
-        for (MdnsServiceBrowserListener listener : listeners) {
+        for (int i = 0; i < listeners.size(); i++) {
+            final MdnsServiceBrowserListener listener = listeners.keyAt(i);
             if (newServiceFound) {
                 listener.onServiceNameDiscovered(serviceInfo);
             }
 
-            if (currentResponse.isComplete()) {
+            if (response.isComplete()) {
                 if (newServiceFound || serviceBecomesComplete) {
                     listener.onServiceFound(serviceInfo);
                 } else {
@@ -270,7 +294,8 @@
         if (response == null) {
             return;
         }
-        for (MdnsServiceBrowserListener listener : listeners) {
+        for (int i = 0; i < listeners.size(); i++) {
+            final MdnsServiceBrowserListener listener = listeners.keyAt(i);
             final MdnsServiceInfo serviceInfo =
                     buildMdnsServiceInfoFromResponse(response, serviceTypeLabels);
             if (response.isComplete()) {
@@ -389,6 +414,29 @@
         }
     }
 
+    private List<MdnsResponse> makeResponsesForResolveIfUnknown(int interfaceIndex,
+            @NonNull Network network) {
+        final List<MdnsResponse> resolveResponses = new ArrayList<>();
+        for (int i = 0; i < listeners.size(); i++) {
+            final String resolveName = listeners.valueAt(i).getResolveInstanceName();
+            if (resolveName == null) {
+                continue;
+            }
+            MdnsResponse knownResponse = instanceNameToResponse.get(resolveName);
+            if (knownResponse == null) {
+                final ArrayList<String> instanceFullName = new ArrayList<>(
+                        serviceTypeLabels.length + 1);
+                instanceFullName.add(resolveName);
+                instanceFullName.addAll(Arrays.asList(serviceTypeLabels));
+                knownResponse = new MdnsResponse(
+                        0L /* lastUpdateTime */, instanceFullName.toArray(new String[0]),
+                        interfaceIndex, network);
+            }
+            resolveResponses.add(knownResponse);
+        }
+        return resolveResponses;
+    }
+
     // A FutureTask that enqueues a single query, and schedule a new FutureTask for the next task.
     private class QueryTask implements Runnable {
 
@@ -400,6 +448,18 @@
 
         @Override
         public void run() {
+            final List<MdnsResponse> servicesToResolve;
+            final boolean sendDiscoveryQueries;
+            synchronized (lock) {
+                // The listener is requesting to resolve a service that has no info in
+                // cache. Use the provided name to generate a minimal response, so other records are
+                // queried to complete it.
+                // Only the names are used to know which queries to send, other parameters like
+                // interfaceIndex do not matter.
+                servicesToResolve = makeResponsesForResolveIfUnknown(
+                        0 /* interfaceIndex */, config.network);
+                sendDiscoveryQueries = servicesToResolve.size() < listeners.size();
+            }
             Pair<Integer, List<String>> result;
             try {
                 result =
@@ -410,7 +470,9 @@
                                 config.subtypes,
                                 config.expectUnicastResponse,
                                 config.transactionId,
-                                config.network)
+                                config.network,
+                                sendDiscoveryQueries,
+                                servicesToResolve)
                                 .call();
             } catch (RuntimeException e) {
                 LOGGER.e(String.format("Failed to run EnqueueMdnsQueryCallable for subtype: %s",
@@ -435,8 +497,8 @@
                     }
                 }
                 if ((result != null)) {
-                    for (MdnsServiceBrowserListener listener : listeners) {
-                        listener.onDiscoveryQuerySent(result.second, result.first);
+                    for (int i = 0; i < listeners.size(); i++) {
+                        listeners.keyAt(i).onDiscoveryQuerySent(result.second, result.first);
                     }
                 }
                 if (shouldRemoveServiceAfterTtlExpires()) {
@@ -446,10 +508,11 @@
                         if (existingResponse.hasServiceRecord()
                                 && existingResponse
                                 .getServiceRecord()
-                                .getRemainingTTL(SystemClock.elapsedRealtime())
+                                .getRemainingTTL(clock.elapsedRealtime())
                                 == 0) {
                             iter.remove();
-                            for (MdnsServiceBrowserListener listener : listeners) {
+                            for (int i = 0; i < listeners.size(); i++) {
+                                final MdnsServiceBrowserListener listener = listeners.keyAt(i);
                                 String serviceInstanceName =
                                         existingResponse.getServiceInstanceName();
                                 if (serviceInstanceName != null) {
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsSocketClient.java b/service-t/src/com/android/server/connectivity/mdns/MdnsSocketClient.java
index 907687e..783b18a 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsSocketClient.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsSocketClient.java
@@ -16,8 +16,6 @@
 
 package com.android.server.connectivity.mdns;
 
-import static com.android.server.connectivity.mdns.MdnsSocketClientBase.Callback;
-
 import android.Manifest.permission;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -35,7 +33,6 @@
 import java.net.DatagramPacket;
 import java.util.ArrayDeque;
 import java.util.ArrayList;
-import java.util.LinkedList;
 import java.util.List;
 import java.util.Queue;
 import java.util.Timer;
@@ -73,7 +70,6 @@
     private final Context context;
     private final byte[] multicastReceiverBuffer = new byte[RECEIVER_BUFFER_SIZE];
     @Nullable private final byte[] unicastReceiverBuffer;
-    private final MdnsResponseDecoder responseDecoder;
     private final MulticastLock multicastLock;
     private final boolean useSeparateSocketForUnicast =
             MdnsConfigs.useSeparateSocketToSendUnicastQuery();
@@ -110,7 +106,6 @@
     public MdnsSocketClient(@NonNull Context context, @NonNull MulticastLock multicastLock) {
         this.context = context;
         this.multicastLock = multicastLock;
-        responseDecoder = new MdnsResponseDecoder(new MdnsResponseDecoder.Clock(), null);
         if (useSeparateSocketForUnicast) {
             unicastReceiverBuffer = new byte[RECEIVER_BUFFER_SIZE];
         } else {
@@ -421,37 +416,27 @@
             int interfaceIndex, @Nullable Network network) {
         int packetNumber = ++receivedPacketNumber;
 
-        List<MdnsResponse> responses = new LinkedList<>();
-        int errorCode = responseDecoder.decode(packet, responses, interfaceIndex, network);
-        if (errorCode == MdnsResponseDecoder.SUCCESS) {
-            if (responseType.equals(MULTICAST_TYPE)) {
-                receivedMulticastResponse = true;
-                if (cannotReceiveMulticastResponse.getAndSet(false)) {
-                    // If we are already in the bad state, receiving a multicast response means
-                    // we are recovered.
-                    LOGGER.e(
-                            "Recovered from the state where the phone can't receive any multicast"
-                                    + " response");
-                }
-            } else {
-                receivedUnicastResponse = true;
-            }
-            for (MdnsResponse response : responses) {
-                String serviceInstanceName = response.getServiceInstanceName();
-                LOGGER.log("mDNS %s response received: %s at ifIndex %d", responseType,
-                        serviceInstanceName, interfaceIndex);
-                if (callback != null) {
-                    callback.onResponseReceived(response);
-                }
-            }
-        } else if (errorCode != MdnsResponseErrorCode.ERROR_NOT_RESPONSE_MESSAGE) {
+        final MdnsPacket response;
+        try {
+            response = MdnsResponseDecoder.parseResponse(packet.getData(), packet.getLength());
+        } catch (MdnsPacket.ParseException e) {
             LOGGER.w(String.format("Error while decoding %s packet (%d): %d",
-                    responseType, packetNumber, errorCode));
+                    responseType, packetNumber, e.code));
             if (callback != null) {
-                callback.onFailedToParseMdnsResponse(packetNumber, errorCode);
+                callback.onFailedToParseMdnsResponse(packetNumber, e.code, network);
             }
+            return e.code;
         }
-        return errorCode;
+
+        if (response == null) {
+            return MdnsResponseErrorCode.ERROR_NOT_RESPONSE_MESSAGE;
+        }
+
+        if (callback != null) {
+            callback.onResponseReceived(response, interfaceIndex, network);
+        }
+
+        return MdnsResponseErrorCode.SUCCESS;
     }
 
     @VisibleForTesting
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsSocketClientBase.java b/service-t/src/com/android/server/connectivity/mdns/MdnsSocketClientBase.java
index 23504a0..ebafc49 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsSocketClientBase.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsSocketClientBase.java
@@ -64,7 +64,9 @@
 
     /*** Notify that the given network is requested for mdns discovery / resolution */
     default void notifyNetworkRequested(@NonNull MdnsServiceBrowserListener listener,
-            @Nullable Network network) { }
+            @Nullable Network network, @NonNull SocketCreationCallback socketCreationCallback) {
+        socketCreationCallback.onSocketCreated(network);
+    }
 
     /*** Notify that the network is unrequested */
     default void notifyNetworkUnrequested(@NonNull MdnsServiceBrowserListener listener) { }
@@ -72,9 +74,17 @@
     /*** Callback for mdns response  */
     interface Callback {
         /*** Receive a mdns response */
-        void onResponseReceived(@NonNull MdnsResponse response);
+        void onResponseReceived(@NonNull MdnsPacket packet, int interfaceIndex,
+                @Nullable Network network);
 
         /*** Parse a mdns response failed */
-        void onFailedToParseMdnsResponse(int receivedPacketNumber, int errorCode);
+        void onFailedToParseMdnsResponse(int receivedPacketNumber, int errorCode,
+                @Nullable Network network);
+    }
+
+    /*** Callback for requested socket creation  */
+    interface SocketCreationCallback {
+        /*** Notify requested socket is created */
+        void onSocketCreated(@Nullable Network network);
     }
 }
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsSocketProvider.java b/service-t/src/com/android/server/connectivity/mdns/MdnsSocketProvider.java
index 9298852..743f946 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsSocketProvider.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsSocketProvider.java
@@ -82,6 +82,7 @@
     private final List<String> mTetheredInterfaces = new ArrayList<>();
     private final byte[] mPacketReadBuffer = new byte[READ_BUFFER_SIZE];
     private boolean mMonitoringSockets = false;
+    private boolean mRequestStop = false;
 
     public MdnsSocketProvider(@NonNull Context context, @NonNull Looper looper) {
         this(context, looper, new Dependencies());
@@ -179,6 +180,7 @@
     /*** Start monitoring sockets by listening callbacks for sockets creation or removal */
     public void startMonitoringSockets() {
         ensureRunningOnHandlerThread(mHandler);
+        mRequestStop = false; // Reset stop request flag.
         if (mMonitoringSockets) {
             Log.d(TAG, "Already monitoring sockets.");
             return;
@@ -195,22 +197,34 @@
         mMonitoringSockets = true;
     }
 
-    /*** Stop monitoring sockets and unregister callbacks */
-    public void stopMonitoringSockets() {
+    private void maybeStopMonitoringSockets() {
+        if (!mMonitoringSockets) return; // Already unregistered.
+        if (!mRequestStop) return; // No stop request.
+
+        // Only unregister the network callback if there is no socket request.
+        if (mCallbacksToRequestedNetworks.isEmpty()) {
+            mContext.getSystemService(ConnectivityManager.class)
+                    .unregisterNetworkCallback(mNetworkCallback);
+
+            final TetheringManager tetheringManager = mContext.getSystemService(
+                    TetheringManager.class);
+            tetheringManager.unregisterTetheringEventCallback(mTetheringEventCallback);
+
+            mHandler.post(mNetlinkMonitor::stop);
+            mMonitoringSockets = false;
+        }
+    }
+
+    /*** Request to stop monitoring sockets and unregister callbacks */
+    public void requestStopWhenInactive() {
         ensureRunningOnHandlerThread(mHandler);
         if (!mMonitoringSockets) {
             Log.d(TAG, "Monitoring sockets hasn't been started.");
             return;
         }
-        if (DBG) Log.d(TAG, "Stop monitoring sockets.");
-        mContext.getSystemService(ConnectivityManager.class)
-                .unregisterNetworkCallback(mNetworkCallback);
-
-        final TetheringManager tetheringManager = mContext.getSystemService(TetheringManager.class);
-        tetheringManager.unregisterTetheringEventCallback(mTetheringEventCallback);
-
-        mHandler.post(mNetlinkMonitor::stop);
-        mMonitoringSockets = false;
+        if (DBG) Log.d(TAG, "Try to stop monitoring sockets.");
+        mRequestStop = true;
+        maybeStopMonitoringSockets();
     }
 
     /*** Check whether the target network is matched current network */
@@ -450,6 +464,9 @@
             cb.onInterfaceDestroyed(new Network(INetd.LOCAL_NET_ID), info.mSocket);
         }
         mTetherInterfaceSockets.clear();
+
+        // Try to unregister network callback.
+        maybeStopMonitoringSockets();
     }
 
     /*** Callbacks for listening socket changes */
diff --git a/service-t/src/com/android/server/ethernet/EthernetServiceImpl.java b/service-t/src/com/android/server/ethernet/EthernetServiceImpl.java
index f6a55c8..e7af569 100644
--- a/service-t/src/com/android/server/ethernet/EthernetServiceImpl.java
+++ b/service-t/src/com/android/server/ethernet/EthernetServiceImpl.java
@@ -92,7 +92,7 @@
     @Override
     public String[] getAvailableInterfaces() throws RemoteException {
         PermissionUtils.enforceAccessNetworkStatePermission(mContext, TAG);
-        return mTracker.getInterfaces(checkUseRestrictedNetworksPermission());
+        return mTracker.getClientModeInterfaces(checkUseRestrictedNetworksPermission());
     }
 
     /**
diff --git a/service-t/src/com/android/server/ethernet/EthernetTracker.java b/service-t/src/com/android/server/ethernet/EthernetTracker.java
index 852cf42..d520757 100644
--- a/service-t/src/com/android/server/ethernet/EthernetTracker.java
+++ b/service-t/src/com/android/server/ethernet/EthernetTracker.java
@@ -385,7 +385,7 @@
         return mFactory.hasInterface(iface);
     }
 
-    String[] getInterfaces(boolean includeRestricted) {
+    String[] getClientModeInterfaces(boolean includeRestricted) {
         return mFactory.getAvailableInterfaces(includeRestricted);
     }
 
@@ -428,9 +428,12 @@
                 // Remote process has already died
                 return;
             }
-            for (String iface : getInterfaces(canUseRestrictedNetworks)) {
+            for (String iface : getClientModeInterfaces(canUseRestrictedNetworks)) {
                 unicastInterfaceStateChange(listener, iface);
             }
+            if (mTetheringInterfaceMode == INTERFACE_MODE_SERVER) {
+                unicastInterfaceStateChange(listener, mTetheringInterface);
+            }
 
             unicastEthernetStateChange(listener, mEthernetState);
         });
diff --git a/service-t/src/com/android/server/net/NetworkStatsFactory.java b/service-t/src/com/android/server/net/NetworkStatsFactory.java
index e0abdf1..5952eae 100644
--- a/service-t/src/com/android/server/net/NetworkStatsFactory.java
+++ b/service-t/src/com/android/server/net/NetworkStatsFactory.java
@@ -34,8 +34,6 @@
 
 import java.io.IOException;
 import java.net.ProtocolException;
-import java.util.Arrays;
-import java.util.HashSet;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 
@@ -147,36 +145,6 @@
     }
 
     /**
-     * Get a set of interfaces containing specified ifaces and stacked interfaces.
-     *
-     * <p>The added stacked interfaces are ifaces stacked on top of the specified ones, or ifaces
-     * on which the specified ones are stacked. Stacked interfaces are those noted with
-     * {@link #noteStackedIface(String, String)}, but only interfaces noted before this method
-     * is called are guaranteed to be included.
-     */
-    public String[] augmentWithStackedInterfaces(@Nullable String[] requiredIfaces) {
-        if (requiredIfaces == NetworkStats.INTERFACES_ALL) {
-            return null;
-        }
-
-        HashSet<String> relatedIfaces = new HashSet<>(Arrays.asList(requiredIfaces));
-        // ConcurrentHashMap's EntrySet iterators are "guaranteed to traverse
-        // elements as they existed upon construction exactly once, and may
-        // (but are not guaranteed to) reflect any modifications subsequent to construction".
-        // This is enough here.
-        for (Map.Entry<String, String> entry : mStackedIfaces.entrySet()) {
-            if (relatedIfaces.contains(entry.getKey())) {
-                relatedIfaces.add(entry.getValue());
-            } else if (relatedIfaces.contains(entry.getValue())) {
-                relatedIfaces.add(entry.getKey());
-            }
-        }
-
-        String[] outArray = new String[relatedIfaces.size()];
-        return relatedIfaces.toArray(outArray);
-    }
-
-    /**
      * Applies 464xlat adjustments with ifaces noted with {@link #noteStackedIface(String, String)}.
      * @see NetworkStats#apply464xlatAdjustments(NetworkStats, NetworkStats, Map)
      */
diff --git a/service-t/src/com/android/server/net/NetworkStatsService.java b/service-t/src/com/android/server/net/NetworkStatsService.java
index 9176ec2..961337d 100644
--- a/service-t/src/com/android/server/net/NetworkStatsService.java
+++ b/service-t/src/com/android/server/net/NetworkStatsService.java
@@ -106,6 +106,7 @@
 import android.net.TetherStatsParcel;
 import android.net.TetheringManager;
 import android.net.TrafficStats;
+import android.net.TransportInfo;
 import android.net.UnderlyingNetworkInfo;
 import android.net.Uri;
 import android.net.netstats.IUsageCallback;
@@ -113,6 +114,7 @@
 import android.net.netstats.provider.INetworkStatsProvider;
 import android.net.netstats.provider.INetworkStatsProviderCallback;
 import android.net.netstats.provider.NetworkStatsProvider;
+import android.net.wifi.WifiInfo;
 import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
@@ -537,7 +539,8 @@
                                     BroadcastOptions.makeBasic())
                                     .setDeliveryGroupPolicy(
                                             ConstantsShim.DELIVERY_GROUP_POLICY_MOST_RECENT)
-                                    .setDeferUntilActive(true)
+                                    .setDeferralPolicy(
+                                            ConstantsShim.DEFERRAL_POLICY_UNTIL_ACTIVE)
                                     .toBundle();
                         } catch (UnsupportedApiLevelException e) {
                             Log.wtf(TAG, "Using unsupported API" + e);
@@ -1729,11 +1732,7 @@
         PermissionUtils.enforceNetworkStackPermission(mContext);
         try {
             final String[] ifaceArray = getAllIfacesSinceBoot(transport);
-            // TODO(b/215633405) : mMobileIfaces and mWifiIfaces already contain the stacked
-            // interfaces, so this is not useful, remove it.
-            final String[] ifacesToQuery =
-                    mStatsFactory.augmentWithStackedInterfaces(ifaceArray);
-            final NetworkStats stats = getNetworkStatsUidDetail(ifacesToQuery);
+            final NetworkStats stats = getNetworkStatsUidDetail(ifaceArray);
             // Clear the interfaces of the stats before returning, so callers won't get this
             // information. This is because no caller needs this information for now, and it
             // makes it easier to change the implementation later by using the histories in the
@@ -2125,6 +2124,14 @@
             final NetworkIdentity ident = NetworkIdentity.buildNetworkIdentity(mContext, snapshot,
                     isDefault, ratType);
 
+            // If WifiInfo contains a null network key then this identity should not be added into
+            // the network identity set. See b/266598304.
+            final TransportInfo transportInfo = snapshot.getNetworkCapabilities()
+                    .getTransportInfo();
+            if (transportInfo instanceof WifiInfo) {
+                final WifiInfo info = (WifiInfo) transportInfo;
+                if (info.getNetworkKey() == null) continue;
+            }
             // Traffic occurring on the base interface is always counted for
             // both total usage and UID details.
             final String baseIface = snapshot.getLinkProperties().getInterfaceName();
diff --git a/service/Android.bp b/service/Android.bp
index c8d2fdd..1523af9 100644
--- a/service/Android.bp
+++ b/service/Android.bp
@@ -171,7 +171,7 @@
         "androidx.annotation_annotation",
         "connectivity-net-module-utils-bpf",
         "connectivity_native_aidl_interface-lateststable-java",
-        "dnsresolver_aidl_interface-V9-java",
+        "dnsresolver_aidl_interface-V11-java",
         "modules-utils-shell-command-handler",
         "net-utils-device-common",
         "net-utils-device-common-bpf",
diff --git a/service/ServiceConnectivityResources/res/values-eu/strings.xml b/service/ServiceConnectivityResources/res/values-eu/strings.xml
index 9b39fd3..13f9eb4 100644
--- a/service/ServiceConnectivityResources/res/values-eu/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-eu/strings.xml
@@ -36,7 +36,7 @@
     <item msgid="3004933964374161223">"datu-konexioa"</item>
     <item msgid="5624324321165953608">"Wifia"</item>
     <item msgid="5667906231066981731">"Bluetootha"</item>
-    <item msgid="346574747471703768">"Ethernet-a"</item>
+    <item msgid="346574747471703768">"Etherneta"</item>
     <item msgid="5734728378097476003">"VPNa"</item>
   </string-array>
     <string name="network_switch_type_name_unknown" msgid="5116448402191972082">"sare mota ezezaguna"</string>
diff --git a/service/jni/com_android_server_connectivity_ClatCoordinator.cpp b/service/jni/com_android_server_connectivity_ClatCoordinator.cpp
index 5b42659..062d272 100644
--- a/service/jni/com_android_server_connectivity_ClatCoordinator.cpp
+++ b/service/jni/com_android_server_connectivity_ClatCoordinator.cpp
@@ -177,17 +177,24 @@
                                                                               jobject clazz) {
     // Will eventually be bound to htons(ETH_P_IPV6) protocol,
     // but only after appropriate bpf filter is attached.
-    int sock = socket(AF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC, 0);
+    const int sock = socket(AF_PACKET, SOCK_RAW | SOCK_CLOEXEC, 0);
     if (sock < 0) {
         throwIOException(env, "packet socket failed", errno);
         return -1;
     }
-    int on = 1;
+    const int on = 1;
+    // enable tpacket_auxdata cmsg delivery, which includes L2 header length
     if (setsockopt(sock, SOL_PACKET, PACKET_AUXDATA, &on, sizeof(on))) {
         throwIOException(env, "packet socket auxdata enablement failed", errno);
         close(sock);
         return -1;
     }
+    // needed for virtio_net_hdr prepending, which includes checksum metadata
+    if (setsockopt(sock, SOL_PACKET, PACKET_VNET_HDR, &on, sizeof(on))) {
+        throwIOException(env, "packet socket vnet_hdr enablement failed", errno);
+        close(sock);
+        return -1;
+    }
     return sock;
 }
 
diff --git a/service/native/libs/libclat/Android.bp b/service/native/libs/libclat/Android.bp
index 54d40ac..996706e 100644
--- a/service/native/libs/libclat/Android.bp
+++ b/service/native/libs/libclat/Android.bp
@@ -23,6 +23,9 @@
         "clatutils.cpp",
     ],
     stl: "libc++_static",
+    header_libs: [
+        "bpf_headers",
+    ],
     static_libs: [
         "libip_checksum",
     ],
diff --git a/service/native/libs/libclat/clatutils.cpp b/service/native/libs/libclat/clatutils.cpp
index c6a9781..6c5c9e3 100644
--- a/service/native/libs/libclat/clatutils.cpp
+++ b/service/native/libs/libclat/clatutils.cpp
@@ -25,6 +25,8 @@
 #include <string.h>
 #include <unistd.h>
 
+#include <bpf/BpfClassic.h>
+
 extern "C" {
 #include "checksum.h"
 }
@@ -33,11 +35,9 @@
 namespace net {
 namespace clat {
 
-bool isIpv4AddressFree(in_addr_t addr) {
-    int s = socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0);
-    if (s == -1) {
-        return 0;
-    }
+bool isIpv4AddressFree(const in_addr_t addr) {
+    const int s = socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0);
+    if (s == -1) return 0;
 
     // Attempt to connect to the address. If the connection succeeds and getsockname returns the
     // same then the address is already assigned to the system and we can't use it.
@@ -47,9 +47,10 @@
             .sin_addr = {addr},
     };
     socklen_t len = sizeof(sin);
-    bool inuse = connect(s, (struct sockaddr*)&sin, sizeof(sin)) == 0 &&
-                 getsockname(s, (struct sockaddr*)&sin, &len) == 0 && (size_t)len >= sizeof(sin) &&
-                 sin.sin_addr.s_addr == addr;
+    const bool inuse = !connect(s, (struct sockaddr*)&sin, sizeof(sin)) &&
+                       !getsockname(s, (struct sockaddr*)&sin, &len) &&
+                       len == (socklen_t)sizeof(sin) &&
+                       sin.sin_addr.s_addr == addr;
 
     close(s);
     return !inuse;
@@ -59,36 +60,30 @@
 //   ip        - the IP address from the configuration file
 //   prefixlen - the length of the prefix from which addresses may be selected.
 //   returns: the IPv4 address, or INADDR_NONE if no addresses were available
-in_addr_t selectIpv4Address(const in_addr ip, int16_t prefixlen) {
+in_addr_t selectIpv4Address(const in_addr ip, const int16_t prefixlen) {
     return selectIpv4AddressInternal(ip, prefixlen, isIpv4AddressFree);
 }
 
 // Only allow testing to use this function directly. Otherwise call selectIpv4Address(ip, pfxlen)
 // which has applied valid isIpv4AddressFree function pointer.
-in_addr_t selectIpv4AddressInternal(const in_addr ip, int16_t prefixlen,
-                                    isIpv4AddrFreeFn isIpv4AddressFreeFunc) {
+in_addr_t selectIpv4AddressInternal(const in_addr ip, const int16_t prefixlen,
+                                    const isIpv4AddrFreeFn isIpv4AddressFreeFunc) {
     // Impossible! Only test allows to apply fn.
-    if (isIpv4AddressFreeFunc == nullptr) {
-        return INADDR_NONE;
-    }
+    if (isIpv4AddressFreeFunc == nullptr) return INADDR_NONE;
 
     // Don't accept prefixes that are too large because we scan addresses one by one.
-    if (prefixlen < 16 || prefixlen > 32) {
-        return INADDR_NONE;
-    }
+    if (prefixlen < 16 || prefixlen > 32) return INADDR_NONE;
 
     // All these are in host byte order.
-    in_addr_t mask = 0xffffffff >> (32 - prefixlen) << (32 - prefixlen);
-    in_addr_t ipv4 = ntohl(ip.s_addr);
-    in_addr_t first_ipv4 = ipv4;
-    in_addr_t prefix = ipv4 & mask;
+    const uint32_t mask = 0xffffffff >> (32 - prefixlen) << (32 - prefixlen);
+    uint32_t ipv4 = ntohl(ip.s_addr);
+    const uint32_t first_ipv4 = ipv4;
+    const uint32_t prefix = ipv4 & mask;
 
     // Pick the first IPv4 address in the pool, wrapping around if necessary.
     // So, for example, 192.0.0.4 -> 192.0.0.5 -> 192.0.0.6 -> 192.0.0.7 -> 192.0.0.0.
     do {
-        if (isIpv4AddressFreeFunc(htonl(ipv4))) {
-            return htonl(ipv4);
-        }
+        if (isIpv4AddressFreeFunc(htonl(ipv4))) return htonl(ipv4);
         ipv4 = prefix | ((ipv4 + 1) & ~mask);
     } while (ipv4 != first_ipv4);
 
@@ -96,7 +91,7 @@
 }
 
 // Alters the bits in the IPv6 address to make them checksum neutral with v4 and nat64Prefix.
-void makeChecksumNeutral(in6_addr* v6, const in_addr v4, const in6_addr& nat64Prefix) {
+void makeChecksumNeutral(in6_addr* const v6, const in_addr v4, const in6_addr& nat64Prefix) {
     // Fill last 8 bytes of IPv6 address with random bits.
     arc4random_buf(&v6->s6_addr[8], 8);
 
@@ -118,33 +113,35 @@
 }
 
 // Picks a random interface ID that is checksum neutral with the IPv4 address and the NAT64 prefix.
-int generateIpv6Address(const char* iface, const in_addr v4, const in6_addr& nat64Prefix,
-                        in6_addr* v6, uint32_t mark) {
-    int s = socket(AF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, 0);
+int generateIpv6Address(const char* const iface, const in_addr v4, const in6_addr& nat64Prefix,
+                        in6_addr* const v6, const uint32_t mark) {
+    const int s = socket(AF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, 0);
     if (s == -1) return -errno;
 
     // Socket's mark affects routing decisions (network selection)
     // An fwmark is necessary for clat to bypass the VPN during initialization.
     if (setsockopt(s, SOL_SOCKET, SO_MARK, &mark, sizeof(mark))) {
-        int ret = errno;
-        ALOGE("setsockopt(SOL_SOCKET, SO_MARK) failed: %s", strerror(errno));
+        const int err = errno;
+        ALOGE("setsockopt(SOL_SOCKET, SO_MARK) failed: %s", strerror(err));
         close(s);
-        return -ret;
+        return -err;
     }
 
-    if (setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE, iface, strlen(iface) + 1) == -1) {
+    if (setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE, iface, strlen(iface) + 1)) {
+        const int err = errno;
+        ALOGE("setsockopt(SOL_SOCKET, SO_BINDTODEVICE, '%s') failed: %s", iface, strerror(err));
         close(s);
-        return -errno;
+        return -err;
     }
 
     sockaddr_in6 sin6 = {.sin6_family = AF_INET6, .sin6_addr = nat64Prefix};
-    if (connect(s, reinterpret_cast<struct sockaddr*>(&sin6), sizeof(sin6)) == -1) {
+    if (connect(s, reinterpret_cast<struct sockaddr*>(&sin6), sizeof(sin6))) {
         close(s);
         return -errno;
     }
 
     socklen_t len = sizeof(sin6);
-    if (getsockname(s, reinterpret_cast<struct sockaddr*>(&sin6), &len) == -1) {
+    if (getsockname(s, reinterpret_cast<struct sockaddr*>(&sin6), &len)) {
         close(s);
         return -errno;
     }
@@ -163,21 +160,22 @@
     return 0;
 }
 
-int detect_mtu(const struct in6_addr* plat_subnet, uint32_t plat_suffix, uint32_t mark) {
+int detect_mtu(const struct in6_addr* const plat_subnet, const uint32_t plat_suffix,
+               const uint32_t mark) {
     // Create an IPv6 UDP socket.
-    int s = socket(AF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, 0);
+    const int s = socket(AF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, 0);
     if (s < 0) {
-        int ret = errno;
-        ALOGE("socket(AF_INET6, SOCK_DGRAM, 0) failed: %s", strerror(errno));
-        return -ret;
+        const int err = errno;
+        ALOGE("socket(AF_INET6, SOCK_DGRAM, 0) failed: %s", strerror(err));
+        return -err;
     }
 
     // Socket's mark affects routing decisions (network selection)
     if (setsockopt(s, SOL_SOCKET, SO_MARK, &mark, sizeof(mark))) {
-        int ret = errno;
-        ALOGE("setsockopt(SOL_SOCKET, SO_MARK) failed: %s", strerror(errno));
+        const int err = errno;
+        ALOGE("setsockopt(SOL_SOCKET, SO_MARK) failed: %s", strerror(err));
         close(s);
-        return -ret;
+        return -err;
     }
 
     // Try to connect udp socket to plat_subnet(96 bits):plat_suffix(32 bits)
@@ -187,20 +185,20 @@
     };
     dst.sin6_addr.s6_addr32[3] = plat_suffix;
     if (connect(s, (struct sockaddr*)&dst, sizeof(dst))) {
-        int ret = errno;
-        ALOGE("connect() failed: %s", strerror(errno));
+        const int err = errno;
+        ALOGE("connect() failed: %s", strerror(err));
         close(s);
-        return -ret;
+        return -err;
     }
 
     // Fetch the socket's IPv6 mtu - this is effectively fetching mtu from routing table
     int mtu;
     socklen_t sz_mtu = sizeof(mtu);
     if (getsockopt(s, SOL_IPV6, IPV6_MTU, &mtu, &sz_mtu)) {
-        int ret = errno;
-        ALOGE("getsockopt(SOL_IPV6, IPV6_MTU) failed: %s", strerror(errno));
+        const int err = errno;
+        ALOGE("getsockopt(SOL_IPV6, IPV6_MTU) failed: %s", strerror(err));
         close(s);
-        return -ret;
+        return -err;
     }
     if (sz_mtu != sizeof(mtu)) {
         ALOGE("getsockopt(SOL_IPV6, IPV6_MTU) returned unexpected size: %d", sz_mtu);
@@ -219,34 +217,26 @@
  *   ifindex - index of interface to add the filter to
  * returns: 0 on success, -errno on failure
  */
-int configure_packet_socket(int sock, in6_addr* addr, int ifindex) {
-    uint32_t* ipv6 = addr->s6_addr32;
-
+int configure_packet_socket(const int sock, const in6_addr* const addr, const int ifindex) {
     // clang-format off
     struct sock_filter filter_code[] = {
-    // Load the first four bytes of the IPv6 destination address (starts 24 bytes in).
-    // Compare it against the first four bytes of our IPv6 address, in host byte order (BPF loads
-    // are always in host byte order). If it matches, continue with next instruction (JMP 0). If it
-    // doesn't match, jump ahead to statement that returns 0 (ignore packet). Repeat for the other
-    // three words of the IPv6 address, and if they all match, return full packet (accept packet).
-        BPF_STMT(BPF_LD  | BPF_W   | BPF_ABS,  24),
-        BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K,    htonl(ipv6[0]), 0, 7),
-        BPF_STMT(BPF_LD  | BPF_W   | BPF_ABS,  28),
-        BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K,    htonl(ipv6[1]), 0, 5),
-        BPF_STMT(BPF_LD  | BPF_W   | BPF_ABS,  32),
-        BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K,    htonl(ipv6[2]), 0, 3),
-        BPF_STMT(BPF_LD  | BPF_W   | BPF_ABS,  36),
-        BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K,    htonl(ipv6[3]), 0, 1),
-        BPF_STMT(BPF_RET | BPF_K,              0xFFFFFFFF),
-        BPF_STMT(BPF_RET | BPF_K,              0),
+        BPF_LOAD_IPV6_BE32(daddr.s6_addr32[0]),
+        BPF2_REJECT_IF_NOT_EQUAL(ntohl(addr->s6_addr32[0])),
+        BPF_LOAD_IPV6_BE32(daddr.s6_addr32[1]),
+        BPF2_REJECT_IF_NOT_EQUAL(ntohl(addr->s6_addr32[1])),
+        BPF_LOAD_IPV6_BE32(daddr.s6_addr32[2]),
+        BPF2_REJECT_IF_NOT_EQUAL(ntohl(addr->s6_addr32[2])),
+        BPF_LOAD_IPV6_BE32(daddr.s6_addr32[3]),
+        BPF2_REJECT_IF_NOT_EQUAL(ntohl(addr->s6_addr32[3])),
+        BPF_ACCEPT,
     };
     // clang-format on
     struct sock_fprog filter = {sizeof(filter_code) / sizeof(filter_code[0]), filter_code};
 
     if (setsockopt(sock, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter))) {
-        int res = errno;
-        ALOGE("attach packet filter failed: %s", strerror(errno));
-        return -res;
+        const int err = errno;
+        ALOGE("attach packet filter failed: %s", strerror(err));
+        return -err;
     }
 
     struct sockaddr_ll sll = {
@@ -257,9 +247,9 @@
                     PACKET_OTHERHOST,  // The 464xlat IPv6 address is not assigned to the kernel.
     };
     if (bind(sock, (struct sockaddr*)&sll, sizeof(sll))) {
-        int res = errno;
-        ALOGE("binding packet socket: %s", strerror(errno));
-        return -res;
+        const int err = errno;
+        ALOGE("binding packet socket: %s", strerror(err));
+        return -err;
     }
 
     return 0;
diff --git a/service/native/libs/libclat/clatutils_test.cpp b/service/native/libs/libclat/clatutils_test.cpp
index abd4e81..f4f97db 100644
--- a/service/native/libs/libclat/clatutils_test.cpp
+++ b/service/native/libs/libclat/clatutils_test.cpp
@@ -165,7 +165,7 @@
     TunInterface v6Iface;
     ASSERT_EQ(0, v6Iface.init());
 
-    int s = socket(AF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC, htons(ETH_P_IPV6));
+    const int s = socket(AF_PACKET, SOCK_RAW | SOCK_CLOEXEC, htons(ETH_P_IPV6));
     EXPECT_LE(0, s);
     struct in6_addr addr6;
     EXPECT_EQ(1, inet_pton(AF_INET6, "2001:db8::f00", &addr6));
diff --git a/service/native/libs/libclat/include/libclat/clatutils.h b/service/native/libs/libclat/include/libclat/clatutils.h
index 991b193..6e17e67 100644
--- a/service/native/libs/libclat/include/libclat/clatutils.h
+++ b/service/native/libs/libclat/include/libclat/clatutils.h
@@ -20,17 +20,19 @@
 namespace net {
 namespace clat {
 
-bool isIpv4AddressFree(in_addr_t addr);
-in_addr_t selectIpv4Address(const in_addr ip, int16_t prefixlen);
-void makeChecksumNeutral(in6_addr* v6, const in_addr v4, const in6_addr& nat64Prefix);
-int generateIpv6Address(const char* iface, const in_addr v4, const in6_addr& nat64Prefix,
-                        in6_addr* v6, uint32_t mark);
-int detect_mtu(const struct in6_addr* plat_subnet, uint32_t plat_suffix, uint32_t mark);
-int configure_packet_socket(int sock, in6_addr* addr, int ifindex);
+bool isIpv4AddressFree(const in_addr_t addr);
+in_addr_t selectIpv4Address(const in_addr ip, const int16_t prefixlen);
+void makeChecksumNeutral(in6_addr* const v6, const in_addr v4, const in6_addr& nat64Prefix);
+int generateIpv6Address(const char* const iface, const in_addr v4, const in6_addr& nat64Prefix,
+                        in6_addr* const v6, const uint32_t mark);
+int detect_mtu(const struct in6_addr* const plat_subnet, const uint32_t plat_suffix,
+               const uint32_t mark);
+int configure_packet_socket(const int sock, const in6_addr* const addr, const int ifindex);
 
 // For testing
-typedef bool (*isIpv4AddrFreeFn)(in_addr_t);
-in_addr_t selectIpv4AddressInternal(const in_addr ip, int16_t prefixlen, isIpv4AddrFreeFn fn);
+typedef bool (*isIpv4AddrFreeFn)(const in_addr_t);
+in_addr_t selectIpv4AddressInternal(const in_addr ip, const int16_t prefixlen,
+                                    const isIpv4AddrFreeFn fn);
 
 }  // namespace clat
 }  // namespace net
diff --git a/service/src/com/android/metrics/stats.proto b/service/src/com/android/metrics/stats.proto
index 48b8316..8104632 100644
--- a/service/src/com/android/metrics/stats.proto
+++ b/service/src/com/android/metrics/stats.proto
@@ -284,3 +284,66 @@
     // The latency of selection issued in milli-second
     optional int32 selection_issued_latency_milli = 5;
 }
+
+message NetworkSliceRequestCountSample {
+    // Bitfield representing the network's capability(e.g. NET_CAPABILITY_PRIORITIZE_LATENCY),
+    // defined in packages/modules/Connectivity/framework/src/android/net/NetworkCapabilities.java
+    optional int64 slice_id = 1;
+
+    // Bitfield representing the network's enterprise capability identifier
+    // (e.g. NET_ENTERPRISE_ID_1), defined in
+    // packages/modules/Connectivity/framework/src/android/net/NetworkCapabilities.java
+    optional int32 enterprise_id = 2;
+
+    // number of request for this slice
+    optional int32 request_count = 3;
+
+    // number of apps with outstanding request(s) for this slice
+    optional int32 distinct_app_count = 4;
+}
+
+message NetworkSliceSessionEnded {
+    // Bitfield representing the network's capability(e.g. NET_CAPABILITY_PRIORITIZE_LATENCY),
+    // defined in packages/modules/Connectivity/framework/src/android/net/NetworkCapabilities.java
+    optional int64 slice_id = 1;
+
+    // Bitfield representing the network's enterprise capability identifier
+    // (e.g. NET_ENTERPRISE_ID_1), defined in
+    // packages/modules/Connectivity/framework/src/android/net/NetworkCapabilities.java
+    optional int32 enterprise_id = 2;
+
+    // Number of bytes received at the device on this slice id
+    optional int64 rx_bytes = 3;
+
+    // Number of bytes transmitted by the device on this slice id
+    optional int64 tx_bytes = 4;
+
+    // Number of apps that have used this slice
+    optional int32 number_of_apps = 5;
+
+    // How long(in seconds) this slice has been connected
+    optional int32 slice_connection_duration_sec = 6;
+}
+
+message NetworkSliceDailyDataUsageReported {
+    // Bitfield representing the network's capability(e.g. NET_CAPABILITY_PRIORITIZE_LATENCY),
+    // defined in packages/modules/Connectivity/framework/src/android/net/NetworkCapabilities.java
+    optional int64 slice_id = 1;
+
+    // Bitfield representing the network's enterprise capability identifier
+    // (e.g. NET_ENTERPRISE_ID_1), defined in
+    // packages/modules/Connectivity/framework/src/android/net/NetworkCapabilities.java
+    optional int32 enterprise_id = 2;
+
+    // Number of bytes received at the device on this slice id
+    optional int64 rx_bytes = 3;
+
+    // Number of bytes transmitted by the device on this slice id
+    optional int64 tx_bytes = 4;
+
+    // Number of apps that have used this slice
+    optional int32 number_of_apps = 5;
+
+    // How long(in seconds) this slice has been connected
+    optional int32 slice_connection_duration_sec = 6;
+}
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index a85f2e0..2af30dd 100755
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -113,6 +113,7 @@
 import android.app.BroadcastOptions;
 import android.app.PendingIntent;
 import android.app.admin.DevicePolicyManager;
+import android.app.compat.CompatChanges;
 import android.app.usage.NetworkStatsManager;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -121,6 +122,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
+import android.content.res.XmlResourceParser;
 import android.database.ContentObserver;
 import android.net.CaptivePortal;
 import android.net.CaptivePortalData;
@@ -195,6 +197,7 @@
 import android.net.Uri;
 import android.net.VpnManager;
 import android.net.VpnTransportInfo;
+import android.net.connectivity.ConnectivityCompatChanges;
 import android.net.metrics.IpConnectivityLog;
 import android.net.metrics.NetworkEvent;
 import android.net.netd.aidl.NativeUidRangeConfig;
@@ -269,6 +272,7 @@
 import com.android.networkstack.apishim.ConstantsShim;
 import com.android.networkstack.apishim.common.BroadcastOptionsShim;
 import com.android.networkstack.apishim.common.UnsupportedApiLevelException;
+import com.android.server.connectivity.ApplicationSelfCertifiedNetworkCapabilities;
 import com.android.server.connectivity.AutodestructReference;
 import com.android.server.connectivity.AutomaticOnOffKeepaliveTracker;
 import com.android.server.connectivity.AutomaticOnOffKeepaliveTracker.AutomaticOnOffKeepalive;
@@ -279,6 +283,7 @@
 import com.android.server.connectivity.DnsManager.PrivateDnsValidationUpdate;
 import com.android.server.connectivity.DscpPolicyTracker;
 import com.android.server.connectivity.FullScore;
+import com.android.server.connectivity.InvalidTagException;
 import com.android.server.connectivity.KeepaliveTracker;
 import com.android.server.connectivity.LingerMonitor;
 import com.android.server.connectivity.MockableSystemProperties;
@@ -300,6 +305,8 @@
 
 import libcore.io.IoUtils;
 
+import org.xmlpull.v1.XmlPullParserException;
+
 import java.io.FileDescriptor;
 import java.io.IOException;
 import java.io.PrintWriter;
@@ -902,6 +909,13 @@
     // Only the handler thread is allowed to access this field.
     private long mIngressRateLimit = -1;
 
+    // This is the cache for the packageName -> ApplicationSelfCertifiedNetworkCapabilities. This
+    // value can be accessed from both handler thread and any random binder thread. Therefore,
+    // accessing this value requires holding a lock. The cache is the same across all the users.
+    @GuardedBy("mSelfCertifiedCapabilityCache")
+    private final Map<String, ApplicationSelfCertifiedNetworkCapabilities>
+            mSelfCertifiedCapabilityCache = new HashMap<>();
+
     /**
      * Implements support for the legacy "one network per network type" model.
      *
@@ -1452,6 +1466,20 @@
         public BroadcastOptionsShim makeBroadcastOptionsShim(BroadcastOptions options) {
             return BroadcastOptionsShimImpl.newInstance(options);
         }
+
+        /**
+         * Wrapper method for
+         * {@link android.app.compat.CompatChanges#isChangeEnabled(long, String, UserHandle)}.
+         *
+         * @param changeId    The ID of the compatibility change in question.
+         * @param packageName The package name of the app in question.
+         * @param user        The user that the operation is done for.
+         * @return {@code true} if the change is enabled for the specified package.
+         */
+        public boolean isChangeEnabled(long changeId, @NonNull final String packageName,
+                @NonNull final UserHandle user) {
+            return CompatChanges.isChangeEnabled(changeId, packageName, user);
+        }
     }
 
     public ConnectivityService(Context context) {
@@ -3105,7 +3133,7 @@
             optsShim.setDeliveryGroupPolicy(ConstantsShim.DELIVERY_GROUP_POLICY_MOST_RECENT);
             optsShim.setDeliveryGroupMatchingKey(ConnectivityManager.CONNECTIVITY_ACTION,
                     createDeliveryGroupKeyForConnectivityAction(info));
-            optsShim.setDeferUntilActive(true);
+            optsShim.setDeferralPolicy(ConstantsShim.DEFERRAL_POLICY_UNTIL_ACTIVE);
         } catch (UnsupportedApiLevelException e) {
             Log.wtf(TAG, "Using unsupported API" + e);
         }
@@ -4427,7 +4455,7 @@
         mQosCallbackTracker.handleNetworkReleased(nai.network);
         for (String iface : nai.linkProperties.getAllInterfaceNames()) {
             // Disable wakeup packet monitoring for each interface.
-            wakeupModifyInterface(iface, nai.networkCapabilities, false);
+            wakeupModifyInterface(iface, nai, false);
         }
         nai.networkMonitor().notifyNetworkDisconnected();
         mNetworkAgentInfos.remove(nai);
@@ -5024,9 +5052,6 @@
     @Override
     public void setTestLowTcpPollingTimerForKeepalive(long timeMs) {
         enforceSettingsPermission();
-        if (!Build.isDebuggable()) {
-            throw new IllegalStateException("Is not supported in non-debuggable build");
-        }
 
         if (timeMs > System.currentTimeMillis() + MAX_TEST_LOW_TCP_POLLING_UNTIL_MS) {
             throw new IllegalArgumentException("Argument should not exceed "
@@ -6319,6 +6344,11 @@
         if (isMappedInOemNetworkPreference(packageName)) {
             handleSetOemNetworkPreference(mOemNetworkPreferences, null);
         }
+
+        // Invalidates cache entry when the package is updated.
+        synchronized (mSelfCertifiedCapabilityCache) {
+            mSelfCertifiedCapabilityCache.remove(packageName);
+        }
     }
 
     private final BroadcastReceiver mUserIntentReceiver = new BroadcastReceiver() {
@@ -6655,8 +6685,6 @@
 
         @Override
         public void binderDied() {
-            log("ConnectivityService NetworkRequestInfo binderDied(" +
-                    "uid/pid:" + mUid + "/" + mPid + ", " + mRequests + ", " + mBinder + ")");
             // As an immutable collection, mRequests cannot change by the time the
             // lambda is evaluated on the handler thread so calling .get() from a binder thread
             // is acceptable. Use handleReleaseNetworkRequest and not directly
@@ -6947,8 +6975,72 @@
                 asUid, requests, nr, msgr, binder, callbackFlags, callingAttributionTag);
     }
 
+    private boolean shouldCheckCapabilitiesDeclaration(
+            @NonNull final NetworkCapabilities networkCapabilities, final int callingUid,
+            @NonNull final String callingPackageName) {
+        final UserHandle user = UserHandle.getUserHandleForUid(callingUid);
+        // Only run the check if the change is enabled.
+        if (!mDeps.isChangeEnabled(
+                ConnectivityCompatChanges.ENABLE_SELF_CERTIFIED_CAPABILITIES_DECLARATION,
+                callingPackageName, user)) {
+            return false;
+        }
+
+        return networkCapabilities.hasCapability(
+                NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_BANDWIDTH)
+                || networkCapabilities.hasCapability(
+                NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_LATENCY);
+    }
+
+    private void enforceRequestCapabilitiesDeclaration(@NonNull final String callerPackageName,
+            @NonNull final NetworkCapabilities networkCapabilities) {
+        // This check is added to fix the linter error for "current min is 30", which is not going
+        // to happen because Connectivity service always run in S+.
+        if (!SdkLevel.isAtLeastS()) {
+            Log.wtf(TAG, "Connectivity service should always run in at least SDK S");
+            return;
+        }
+        ApplicationSelfCertifiedNetworkCapabilities applicationNetworkCapabilities;
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            synchronized (mSelfCertifiedCapabilityCache) {
+                applicationNetworkCapabilities = mSelfCertifiedCapabilityCache.get(
+                        callerPackageName);
+                if (applicationNetworkCapabilities == null) {
+                    final PackageManager packageManager = mContext.getPackageManager();
+                    final PackageManager.Property networkSliceProperty = packageManager.getProperty(
+                            ConstantsShim.PROPERTY_SELF_CERTIFIED_NETWORK_CAPABILITIES,
+                            callerPackageName
+                    );
+                    final XmlResourceParser parser = packageManager
+                            .getResourcesForApplication(callerPackageName)
+                            .getXml(networkSliceProperty.getResourceId());
+                    applicationNetworkCapabilities =
+                            ApplicationSelfCertifiedNetworkCapabilities.createFromXml(parser);
+                    mSelfCertifiedCapabilityCache.put(callerPackageName,
+                            applicationNetworkCapabilities);
+                }
+
+            }
+        } catch (PackageManager.NameNotFoundException ne) {
+            throw new SecurityException(
+                    "Cannot find " + ConstantsShim.PROPERTY_SELF_CERTIFIED_NETWORK_CAPABILITIES
+                            + " property");
+        } catch (XmlPullParserException | IOException | InvalidTagException e) {
+            throw new SecurityException(e.getMessage());
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+
+        applicationNetworkCapabilities.enforceSelfCertifiedNetworkCapabilitiesDeclared(
+                networkCapabilities);
+    }
     private void enforceNetworkRequestPermissions(NetworkCapabilities networkCapabilities,
             String callingPackageName, String callingAttributionTag, final int callingUid) {
+        if (shouldCheckCapabilitiesDeclaration(networkCapabilities, callingUid,
+                callingPackageName)) {
+            enforceRequestCapabilitiesDeclaration(callingPackageName, networkCapabilities);
+        }
         if (networkCapabilities.hasCapability(NET_CAPABILITY_NOT_RESTRICTED) == false) {
             // For T+ devices, callers with carrier privilege could request with CBS capabilities.
             if (networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_CBS)
@@ -7706,7 +7798,7 @@
         // the LinkProperties for the network are accurate.
         networkAgent.clatd.fixupLinkProperties(oldLp, newLp);
 
-        updateInterfaces(newLp, oldLp, netId, networkAgent.networkCapabilities);
+        updateInterfaces(newLp, oldLp, netId, networkAgent);
 
         // update filtering rules, need to happen after the interface update so netd knows about the
         // new interface (the interface name -> index map becomes initialized)
@@ -7814,10 +7906,16 @@
         return captivePortalBuilder.build();
     }
 
-    private void wakeupModifyInterface(String iface, NetworkCapabilities caps, boolean add) {
+    private String makeNflogPrefix(String iface, long networkHandle) {
+        // This needs to be kept in sync and backwards compatible with the decoding logic in
+        // NetdEventListenerService, which is non-mainline code.
+        return SdkLevel.isAtLeastU() ? (networkHandle + ":" + iface) : ("iface:" + iface);
+    }
+
+    private void wakeupModifyInterface(String iface, NetworkAgentInfo nai, boolean add) {
         // Marks are only available on WiFi interfaces. Checking for
         // marks on unsupported interfaces is harmless.
-        if (!caps.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
+        if (!nai.networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
             return;
         }
 
@@ -7830,7 +7928,7 @@
             return;
         }
 
-        final String prefix = "iface:" + iface;
+        final String prefix = makeNflogPrefix(iface, nai.network.getNetworkHandle());
         try {
             if (add) {
                 mNetd.wakeupAddInterface(iface, prefix, mark, mask);
@@ -7840,12 +7938,11 @@
         } catch (Exception e) {
             loge("Exception modifying wakeup packet monitoring: " + e);
         }
-
     }
 
     private void updateInterfaces(final @NonNull LinkProperties newLp,
             final @Nullable LinkProperties oldLp, final int netId,
-            final @NonNull NetworkCapabilities caps) {
+            final @NonNull NetworkAgentInfo nai) {
         final CompareResult<String> interfaceDiff = new CompareResult<>(
                 oldLp != null ? oldLp.getAllInterfaceNames() : null, newLp.getAllInterfaceNames());
         if (!interfaceDiff.added.isEmpty()) {
@@ -7853,9 +7950,9 @@
                 try {
                     if (DBG) log("Adding iface " + iface + " to network " + netId);
                     mNetd.networkAddInterface(netId, iface);
-                    wakeupModifyInterface(iface, caps, true);
+                    wakeupModifyInterface(iface, nai, true);
                     mDeps.reportNetworkInterfaceForTransports(mContext, iface,
-                            caps.getTransportTypes());
+                            nai.networkCapabilities.getTransportTypes());
                 } catch (Exception e) {
                     logw("Exception adding interface: " + e);
                 }
@@ -7864,7 +7961,7 @@
         for (final String iface : interfaceDiff.removed) {
             try {
                 if (DBG) log("Removing iface " + iface + " from network " + netId);
-                wakeupModifyInterface(iface, caps, false);
+                wakeupModifyInterface(iface, nai, false);
                 mNetd.networkRemoveInterface(netId, iface);
             } catch (Exception e) {
                 loge("Exception removing interface: " + e);
@@ -8373,6 +8470,7 @@
         exemptUids[1] = nai.networkCapabilities.getOwnerUid();
         UidRangeParcel[] ranges = toUidRangeStableParcels(uidRanges);
 
+        // Close sockets before modifying uid ranges so that RST packets can reach to the server.
         maybeCloseSockets(nai, ranges, exemptUids);
         try {
             if (add) {
@@ -8386,6 +8484,7 @@
             loge("Exception while " + (add ? "adding" : "removing") + " uid ranges " + uidRanges +
                     " on netId " + nai.network.netId + ". " + e);
         }
+        // Close sockets that established connection while requesting netd.
         maybeCloseSockets(nai, ranges, exemptUids);
     }
 
diff --git a/service/src/com/android/server/connectivity/ApplicationSelfCertifiedNetworkCapabilities.java b/service/src/com/android/server/connectivity/ApplicationSelfCertifiedNetworkCapabilities.java
new file mode 100644
index 0000000..76e966f
--- /dev/null
+++ b/service/src/com/android/server/connectivity/ApplicationSelfCertifiedNetworkCapabilities.java
@@ -0,0 +1,209 @@
+/*
+ * 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 com.android.server.connectivity;
+
+
+import android.annotation.NonNull;
+import android.net.NetworkCapabilities;
+import android.util.Log;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.ArrayDeque;
+
+
+/**
+ * The class for parsing and checking the self-declared application network capabilities.
+ *
+ * ApplicationSelfCertifiedNetworkCapabilities is an immutable class that
+ * can parse the self-declared application network capabilities in the application resources. The
+ * class also provides a helper method to check whether the requested network capabilities
+ * already self-declared.
+ */
+public final class ApplicationSelfCertifiedNetworkCapabilities {
+
+    public static final String PRIORITIZE_LATENCY = "NET_CAPABILITY_PRIORITIZE_LATENCY";
+    public static final String PRIORITIZE_BANDWIDTH = "NET_CAPABILITY_PRIORITIZE_BANDWIDTH";
+
+    private static final String TAG =
+            ApplicationSelfCertifiedNetworkCapabilities.class.getSimpleName();
+    private static final String NETWORK_CAPABILITIES_DECLARATION_TAG =
+            "network-capabilities-declaration";
+    private static final String USES_NETWORK_CAPABILITY_TAG = "uses-network-capability";
+    private static final String NAME_TAG = "name";
+
+    private long mRequestedNetworkCapabilities = 0;
+
+    /**
+     * Creates {@link ApplicationSelfCertifiedNetworkCapabilities} from a xml parser.
+     *
+     * <p> Here is an example of the xml syntax:
+     *
+     * <pre>
+     * {@code
+     *  <network-capabilities-declaration xmlns:android="http://schemas.android.com/apk/res/android">
+     *     <uses-network-capability android:name="NET_CAPABILITY_PRIORITIZE_LATENCY"/>
+     *     <uses-network-capability android:name="NET_CAPABILITY_PRIORITIZE_BANDWIDTH"/>
+     * </network-capabilities-declaration>
+     * }
+     * </pre>
+     * <p>
+     *
+     * @param xmlParser The underlying {@link XmlPullParser} that will read the xml.
+     * @return An ApplicationSelfCertifiedNetworkCapabilities object.
+     * @throws InvalidTagException    if the capabilities in xml config contains invalid tag.
+     * @throws XmlPullParserException if xml parsing failed.
+     * @throws IOException            if unable to read the xml file properly.
+     */
+    @NonNull
+    public static ApplicationSelfCertifiedNetworkCapabilities createFromXml(
+            @NonNull final XmlPullParser xmlParser)
+            throws InvalidTagException, XmlPullParserException, IOException {
+        return new ApplicationSelfCertifiedNetworkCapabilities(parseXml(xmlParser));
+    }
+
+    private static long parseXml(@NonNull final XmlPullParser xmlParser)
+            throws InvalidTagException, XmlPullParserException, IOException {
+        long requestedNetworkCapabilities = 0;
+        final ArrayDeque<String> openTags = new ArrayDeque<>();
+
+        while (checkedNextTag(xmlParser, openTags) != XmlPullParser.START_TAG) {
+            continue;
+        }
+
+        // Validates the tag is "network-capabilities-declaration"
+        if (!xmlParser.getName().equals(NETWORK_CAPABILITIES_DECLARATION_TAG)) {
+            throw new InvalidTagException("Invalid tag: " + xmlParser.getName());
+        }
+
+        checkedNextTag(xmlParser, openTags);
+        int eventType = xmlParser.getEventType();
+        while (eventType != XmlPullParser.END_DOCUMENT) {
+            switch (eventType) {
+                case XmlPullParser.START_TAG:
+                    // USES_NETWORK_CAPABILITY_TAG should directly be declared under
+                    // NETWORK_CAPABILITIES_DECLARATION_TAG.
+                    if (xmlParser.getName().equals(USES_NETWORK_CAPABILITY_TAG)
+                            && openTags.size() == 1) {
+                        int capability = parseDeclarationTag(xmlParser);
+                        if (capability >= 0) {
+                            requestedNetworkCapabilities |= 1L << capability;
+                        }
+                    } else {
+                        Log.w(TAG, "Unknown tag: " + xmlParser.getName() + " ,tags stack size: "
+                                + openTags.size());
+                    }
+                    break;
+                default:
+                    break;
+            }
+            eventType = checkedNextTag(xmlParser, openTags);
+        }
+        // Checks all the tags are parsed.
+        if (!openTags.isEmpty()) {
+            throw new InvalidTagException("Unbalanced tag: " + openTags.peek());
+        }
+        return requestedNetworkCapabilities;
+    }
+
+    private static int parseDeclarationTag(@NonNull final XmlPullParser xmlParser) {
+        String name = null;
+        for (int i = 0; i < xmlParser.getAttributeCount(); i++) {
+            final String attrName = xmlParser.getAttributeName(i);
+            if (attrName.equals(NAME_TAG)) {
+                name = xmlParser.getAttributeValue(i);
+            } else {
+                Log.w(TAG, "Unknown attribute name: " + attrName);
+            }
+        }
+        if (name != null) {
+            switch (name) {
+                case PRIORITIZE_BANDWIDTH:
+                    return NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_BANDWIDTH;
+                case PRIORITIZE_LATENCY:
+                    return NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_LATENCY;
+                default:
+                    Log.w(TAG, "Unknown capability declaration name: " + name);
+            }
+        } else {
+            Log.w(TAG, "uses-network-capability name must be specified");
+        }
+        // Invalid capability
+        return -1;
+    }
+
+    private static int checkedNextTag(@NonNull final XmlPullParser xmlParser,
+            @NonNull final ArrayDeque<String> openTags)
+            throws XmlPullParserException, IOException, InvalidTagException {
+        if (xmlParser.getEventType() == XmlPullParser.START_TAG) {
+            openTags.addFirst(xmlParser.getName());
+        } else if (xmlParser.getEventType() == XmlPullParser.END_TAG) {
+            if (!openTags.isEmpty() && openTags.peekFirst().equals(xmlParser.getName())) {
+                openTags.removeFirst();
+            } else {
+                throw new InvalidTagException("Unbalanced tag: " + xmlParser.getName());
+            }
+        }
+        return xmlParser.next();
+    }
+
+    private ApplicationSelfCertifiedNetworkCapabilities(long requestedNetworkCapabilities) {
+        mRequestedNetworkCapabilities = requestedNetworkCapabilities;
+    }
+
+    /**
+     * Enforces self-certified capabilities are declared.
+     *
+     * @param networkCapabilities the input NetworkCapabilities to check against.
+     * @throws SecurityException if the capabilities are not properly self-declared.
+     */
+    public void enforceSelfCertifiedNetworkCapabilitiesDeclared(
+            @NonNull final NetworkCapabilities networkCapabilities) {
+        if (networkCapabilities.hasCapability(
+                NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_BANDWIDTH)
+                && !hasPrioritizeBandwidth()) {
+            throw new SecurityException(
+                    "Missing " + ApplicationSelfCertifiedNetworkCapabilities.PRIORITIZE_BANDWIDTH
+                            + " declaration");
+        }
+        if (networkCapabilities.hasCapability(
+                NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_LATENCY)
+                && !hasPrioritizeLatency()) {
+            throw new SecurityException(
+                    "Missing " + ApplicationSelfCertifiedNetworkCapabilities.PRIORITIZE_LATENCY
+                            + " declaration");
+        }
+    }
+
+    /**
+     * Checks if NET_CAPABILITY_PRIORITIZE_LATENCY is declared.
+     */
+    private boolean hasPrioritizeLatency() {
+        return (mRequestedNetworkCapabilities & (1L
+                << NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_LATENCY)) != 0;
+    }
+
+    /**
+     * Checks if NET_CAPABILITY_PRIORITIZE_BANDWIDTH is declared.
+     */
+    private boolean hasPrioritizeBandwidth() {
+        return (mRequestedNetworkCapabilities & (1L
+                << NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_BANDWIDTH)) != 0;
+    }
+}
diff --git a/service/src/com/android/server/connectivity/AutomaticOnOffKeepaliveTracker.java b/service/src/com/android/server/connectivity/AutomaticOnOffKeepaliveTracker.java
index 9f9b496..6b7222a 100644
--- a/service/src/com/android/server/connectivity/AutomaticOnOffKeepaliveTracker.java
+++ b/service/src/com/android/server/connectivity/AutomaticOnOffKeepaliveTracker.java
@@ -88,7 +88,6 @@
 public class AutomaticOnOffKeepaliveTracker {
     private static final String TAG = "AutomaticOnOffKeepaliveTracker";
     private static final int[] ADDRESS_FAMILIES = new int[] {AF_INET6, AF_INET};
-    private static final String EXTRA_BINDER_TOKEN = "token";
     private static final long DEFAULT_TCP_POLLING_INTERVAL_MS = 120_000L;
     private static final long LOW_TCP_POLLING_INTERVAL_MS = 1_000L;
     private static final String AUTOMATIC_ON_OFF_KEEPALIVE_VERSION =
diff --git a/service/src/com/android/server/connectivity/InvalidTagException.java b/service/src/com/android/server/connectivity/InvalidTagException.java
new file mode 100644
index 0000000..b924d27
--- /dev/null
+++ b/service/src/com/android/server/connectivity/InvalidTagException.java
@@ -0,0 +1,28 @@
+/*
+ * 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 com.android.server.connectivity;
+
+
+/**
+ * An exception thrown when a Tag is not valid in self_certified_network_capabilities.xml.
+ */
+public class InvalidTagException extends Exception {
+
+    public InvalidTagException(String message) {
+        super(message);
+    }
+}
diff --git a/tests/common/java/android/net/netstats/NetworkTemplateTest.kt b/tests/common/java/android/net/netstats/NetworkTemplateTest.kt
index c2eacbc..fb6759e 100644
--- a/tests/common/java/android/net/netstats/NetworkTemplateTest.kt
+++ b/tests/common/java/android/net/netstats/NetworkTemplateTest.kt
@@ -165,23 +165,6 @@
     }
 
     @Test
-    fun testUnsupportedAppUsageConstructor() {
-        val templateMobile = NetworkTemplate(MATCH_MOBILE, null /* subscriberId */,
-                null /* wifiNetworkKey */)
-        val templateMobileWildcard = NetworkTemplate(6 /* MATCH_MOBILE_WILDCARD */,
-                null /* subscriberId */, null /* wifiNetworkKey */)
-        val templateWifiWildcard = NetworkTemplate(7 /* MATCH_WIFI_WILDCARD */,
-                null /* subscriberId */,
-                null /* wifiNetworkKey */)
-
-        assertEquals(NetworkTemplate.Builder(MATCH_MOBILE).setMeteredness(METERED_YES).build(),
-                templateMobile)
-        assertEquals(NetworkTemplate.Builder(MATCH_MOBILE).setMeteredness(METERED_YES).build(),
-                templateMobileWildcard)
-        assertEquals(NetworkTemplate.Builder(MATCH_WIFI).build(), templateWifiWildcard)
-    }
-
-    @Test
     fun testBuilderWifiNetworkKeys() {
         // Verify template builder which generates same template with the given different
         // sequence keys.
diff --git a/tests/cts/hostside/Android.bp b/tests/cts/hostside/Android.bp
index ff9bd31..891c2dd 100644
--- a/tests/cts/hostside/Android.bp
+++ b/tests/cts/hostside/Android.bp
@@ -47,6 +47,9 @@
     data: [
         ":CtsHostsideNetworkTestsApp",
         ":CtsHostsideNetworkTestsApp2",
+        ":CtsHostsideNetworkCapTestsAppWithoutProperty",
+        ":CtsHostsideNetworkCapTestsAppWithProperty",
+        ":CtsHostsideNetworkCapTestsAppSdk33",
     ] + next_app_data,
     per_testcase_directory: true,
 }
diff --git a/tests/cts/hostside/TEST_MAPPING b/tests/cts/hostside/TEST_MAPPING
index ab6de82..2cfd7af 100644
--- a/tests/cts/hostside/TEST_MAPPING
+++ b/tests/cts/hostside/TEST_MAPPING
@@ -8,6 +8,9 @@
         },
         {
           "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+        },
+        {
+          "exclude-annotation": "android.platform.test.annotations.RequiresDevice"
         }
       ]
     }
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java
index a5bf000..b6902b5 100755
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java
@@ -18,6 +18,8 @@
 
 import static android.Manifest.permission.MANAGE_TEST_NETWORKS;
 import static android.Manifest.permission.NETWORK_SETTINGS;
+import static android.Manifest.permission.READ_DEVICE_CONFIG;
+import static android.Manifest.permission.WRITE_DEVICE_CONFIG;
 import static android.content.pm.PackageManager.FEATURE_TELEPHONY;
 import static android.content.pm.PackageManager.FEATURE_WIFI;
 import static android.net.ConnectivityManager.TYPE_VPN;
@@ -36,6 +38,12 @@
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
 import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+import static com.android.cts.net.hostside.VpnTest.TestSocketKeepaliveCallback.CallbackType.ON_DATA_RECEIVED;
+import static com.android.cts.net.hostside.VpnTest.TestSocketKeepaliveCallback.CallbackType.ON_ERROR;
+import static com.android.cts.net.hostside.VpnTest.TestSocketKeepaliveCallback.CallbackType.ON_PAUSED;
+import static com.android.cts.net.hostside.VpnTest.TestSocketKeepaliveCallback.CallbackType.ON_RESUMED;
+import static com.android.cts.net.hostside.VpnTest.TestSocketKeepaliveCallback.CallbackType.ON_STARTED;
+import static com.android.cts.net.hostside.VpnTest.TestSocketKeepaliveCallback.CallbackType.ON_STOPPED;
 import static com.android.networkstack.apishim.ConstantsShim.BLOCKED_REASON_LOCKDOWN_VPN;
 import static com.android.networkstack.apishim.ConstantsShim.BLOCKED_REASON_NONE;
 import static com.android.networkstack.apishim.ConstantsShim.RECEIVER_EXPORTED;
@@ -64,6 +72,7 @@
 import android.database.Cursor;
 import android.net.ConnectivityManager;
 import android.net.ConnectivityManager.NetworkCallback;
+import android.net.IpSecManager;
 import android.net.LinkAddress;
 import android.net.LinkProperties;
 import android.net.Network;
@@ -71,6 +80,7 @@
 import android.net.NetworkRequest;
 import android.net.Proxy;
 import android.net.ProxyInfo;
+import android.net.SocketKeepalive;
 import android.net.TestNetworkInterface;
 import android.net.TestNetworkManager;
 import android.net.TransportInfo;
@@ -79,6 +89,7 @@
 import android.net.VpnService;
 import android.net.VpnTransportInfo;
 import android.net.cts.util.CtsNetUtils;
+import android.net.util.KeepaliveUtils;
 import android.net.wifi.WifiManager;
 import android.os.Build;
 import android.os.Handler;
@@ -87,6 +98,7 @@
 import android.os.Process;
 import android.os.SystemProperties;
 import android.os.UserHandle;
+import android.provider.DeviceConfig;
 import android.provider.Settings;
 import android.support.test.uiautomator.UiDevice;
 import android.support.test.uiautomator.UiObject;
@@ -105,6 +117,8 @@
 
 import com.android.compatibility.common.util.BlockingBroadcastReceiver;
 import com.android.modules.utils.build.SdkLevel;
+import com.android.net.module.util.ArrayTrackRecord;
+import com.android.net.module.util.CollectionUtils;
 import com.android.net.module.util.PacketBuilder;
 import com.android.testutils.DevSdkIgnoreRule;
 import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
@@ -141,6 +155,7 @@
 import java.util.UUID;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
 import java.util.concurrent.TimeUnit;
 
 /**
@@ -190,6 +205,12 @@
     public static int SOCKET_TIMEOUT_MS = 100;
     public static String TEST_HOST = "connectivitycheck.gstatic.com";
 
+    private static final String AUTOMATIC_ON_OFF_KEEPALIVE_VERSION =
+                "automatic_on_off_keepalive_version";
+    // Enabled since version 1 means it's always enabled because the version is always above 1
+    private static final String AUTOMATIC_ON_OFF_KEEPALIVE_ENABLED = "1";
+    private static final long TEST_TCP_POLLING_TIMER_EXPIRED_PERIOD_MS = 60_000L;
+
     private UiDevice mDevice;
     private MyActivity mActivity;
     private String mPackageName;
@@ -201,13 +222,15 @@
     private Context mTestContext;
     private Context mTargetContext;
     Network mNetwork;
-    NetworkCallback mCallback;
     final Object mLock = new Object();
     final Object mLockShutdown = new Object();
 
     private String mOldPrivateDnsMode;
     private String mOldPrivateDnsSpecifier;
 
+    // The registered callbacks.
+    private List<NetworkCallback> mRegisteredCallbacks = new ArrayList<>();
+
     @Rule
     public final DevSdkIgnoreRule mDevSdkIgnoreRule = new DevSdkIgnoreRule();
 
@@ -228,7 +251,6 @@
     @Before
     public void setUp() throws Exception {
         mNetwork = null;
-        mCallback = null;
         mTestContext = getInstrumentation().getContext();
         mTargetContext = getInstrumentation().getTargetContext();
         storePrivateDnsSetting();
@@ -248,15 +270,40 @@
     public void tearDown() throws Exception {
         restorePrivateDnsSetting();
         mRemoteSocketFactoryClient.unbind();
-        if (mCallback != null) {
-            mCM.unregisterNetworkCallback(mCallback);
-        }
         mCtsNetUtils.tearDown();
         Log.i(TAG, "Stopping VPN");
         stopVpn();
+        unregisterRegisteredCallbacks();
         mActivity.finish();
     }
 
+    private void registerNetworkCallback(NetworkRequest request, NetworkCallback callback) {
+        mCM.registerNetworkCallback(request, callback);
+        mRegisteredCallbacks.add(callback);
+    }
+
+    private void registerDefaultNetworkCallback(NetworkCallback callback) {
+        mCM.registerDefaultNetworkCallback(callback);
+        mRegisteredCallbacks.add(callback);
+    }
+
+    private void registerSystemDefaultNetworkCallback(NetworkCallback callback, Handler h) {
+        mCM.registerSystemDefaultNetworkCallback(callback, h);
+        mRegisteredCallbacks.add(callback);
+    }
+
+    private void registerDefaultNetworkCallbackForUid(int uid, NetworkCallback callback,
+            Handler h) {
+        mCM.registerDefaultNetworkCallbackForUid(uid, callback, h);
+        mRegisteredCallbacks.add(callback);
+    }
+
+    private void unregisterRegisteredCallbacks() {
+        for (NetworkCallback callback: mRegisteredCallbacks) {
+            mCM.unregisterNetworkCallback(callback);
+        }
+    }
+
     private void prepareVpn() throws Exception {
         final int REQUEST_ID = 42;
 
@@ -372,7 +419,7 @@
                 .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
                 .removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
                 .build();
-        mCallback = new NetworkCallback() {
+        final NetworkCallback callback = new NetworkCallback() {
             public void onAvailable(Network network) {
                 synchronized (mLock) {
                     Log.i(TAG, "Got available callback for network=" + network);
@@ -381,7 +428,7 @@
                 }
             }
         };
-        mCM.registerNetworkCallback(request, mCallback);  // Unregistered in tearDown.
+        registerNetworkCallback(request, callback);
 
         // Start the service and wait up for TIMEOUT_MS ms for the VPN to come up.
         establishVpn(addresses, routes, excludedRoutes, allowedApplications, disallowedApplications,
@@ -406,7 +453,7 @@
                 .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
                 .removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
                 .build();
-        mCallback = new NetworkCallback() {
+        final NetworkCallback callback = new NetworkCallback() {
             public void onLost(Network network) {
                 synchronized (mLockShutdown) {
                     Log.i(TAG, "Got lost callback for network=" + network
@@ -417,7 +464,7 @@
                 }
             }
        };
-        mCM.registerNetworkCallback(request, mCallback);  // Unregistered in tearDown.
+        registerNetworkCallback(request, callback);
         // Simply calling mActivity.stopService() won't stop the service, because the system binds
         // to the service for the purpose of sending it a revoke command if another VPN comes up,
         // and stopping a bound service has no effect. Instead, "start" the service again with an
@@ -778,14 +825,10 @@
             }
         };
 
-        mCM.registerNetworkCallback(request, callback);
+        registerNetworkCallback(request, callback);
 
-        try {
-            assertTrue("Private DNS hostname was not " + hostname + " after " + TIMEOUT_MS + "ms",
-                    latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        } finally {
-            mCM.unregisterNetworkCallback(callback);
-        }
+        assertTrue("Private DNS hostname was not " + hostname + " after " + TIMEOUT_MS + "ms",
+                latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
     }
 
     private void setAndVerifyPrivateDns(boolean strictMode) throws Exception {
@@ -872,7 +915,7 @@
                     false /* isAlwaysMetered */);
             // Acquire the NETWORK_SETTINGS permission for getting the underlying networks.
             runWithShellPermissionIdentity(() -> {
-                mCM.registerNetworkCallback(makeVpnNetworkRequest(), callback);
+                registerNetworkCallback(makeVpnNetworkRequest(), callback);
                 // Check that this VPN doesn't have any underlying networks.
                 expectUnderlyingNetworks(callback, new ArrayList<Network>());
 
@@ -905,8 +948,6 @@
                 } else {
                     mCtsNetUtils.ensureWifiDisconnected(null);
                 }
-            }, () -> {
-                mCM.unregisterNetworkCallback(callback);
             });
     }
 
@@ -940,9 +981,9 @@
                     UserHandle.of(5 /* userId */).getUid(Process.FIRST_APPLICATION_UID);
             final Handler h = new Handler(Looper.getMainLooper());
             runWithShellPermissionIdentity(() -> {
-                mCM.registerSystemDefaultNetworkCallback(systemDefaultCallback, h);
-                mCM.registerDefaultNetworkCallbackForUid(otherUid, otherUidCallback, h);
-                mCM.registerDefaultNetworkCallbackForUid(Process.myUid(), myUidCallback, h);
+                registerSystemDefaultNetworkCallback(systemDefaultCallback, h);
+                registerDefaultNetworkCallbackForUid(otherUid, otherUidCallback, h);
+                registerDefaultNetworkCallbackForUid(Process.myUid(), myUidCallback, h);
             }, NETWORK_SETTINGS);
             for (TestableNetworkCallback callback :
                     List.of(systemDefaultCallback, otherUidCallback, myUidCallback)) {
@@ -993,9 +1034,6 @@
             // fail and could cause the default network to switch (e.g., from wifi to cellular).
             systemDefaultCallback.assertNoCallback();
             otherUidCallback.assertNoCallback();
-            mCM.unregisterNetworkCallback(systemDefaultCallback);
-            mCM.unregisterNetworkCallback(otherUidCallback);
-            mCM.unregisterNetworkCallback(myUidCallback);
         }
 
         checkStrictModePrivateDns();
@@ -1024,6 +1062,183 @@
         checkStrictModePrivateDns();
     }
 
+    private int getSupportedKeepalives(NetworkCapabilities nc) throws Exception {
+        // Get number of supported concurrent keepalives for testing network.
+        final int[] keepalivesPerTransport = KeepaliveUtils.getSupportedKeepalives(
+                mTargetContext);
+        return KeepaliveUtils.getSupportedKeepalivesForNetworkCapabilities(
+                keepalivesPerTransport, nc);
+    }
+
+    // This class can't be private, otherwise the constants can't be static imported.
+    static class TestSocketKeepaliveCallback extends SocketKeepalive.Callback {
+        // This must be larger than the alarm delay in AutomaticOnOffKeepaliveTracker.
+        private static final int KEEPALIVE_TIMEOUT_MS = 10_000;
+        public enum CallbackType {
+            ON_STARTED,
+            ON_RESUMED,
+            ON_STOPPED,
+            ON_PAUSED,
+            ON_ERROR,
+            ON_DATA_RECEIVED
+        }
+        private ArrayTrackRecord<CallbackType> mHistory = new ArrayTrackRecord<>();
+        private ArrayTrackRecord<CallbackType>.ReadHead mEvents = mHistory.newReadHead();
+
+        @Override
+        public void onStarted() {
+            mHistory.add(ON_STARTED);
+        }
+
+        @Override
+        public void onResumed() {
+            mHistory.add(ON_RESUMED);
+        }
+
+        @Override
+        public void onStopped() {
+            mHistory.add(ON_STOPPED);
+        }
+
+        @Override
+        public void onPaused() {
+            mHistory.add(ON_PAUSED);
+        }
+
+        @Override
+        public void onError(final int error) {
+            mHistory.add(ON_ERROR);
+        }
+
+        @Override
+        public void onDataReceived() {
+            mHistory.add(ON_DATA_RECEIVED);
+        }
+
+        public CallbackType poll() {
+            return mEvents.poll(KEEPALIVE_TIMEOUT_MS, it -> true);
+        }
+    }
+
+    private InetAddress getV4AddrByName(final String hostname) throws Exception {
+        final InetAddress[] allAddrs = InetAddress.getAllByName(hostname);
+        for (InetAddress addr : allAddrs) {
+            if (addr instanceof Inet4Address) return addr;
+        }
+        return null;
+    }
+
+    @Test
+    public void testAutomaticOnOffKeepaliveModeNoClose() throws Exception {
+        doTestAutomaticOnOffKeepaliveMode(false);
+    }
+
+    @Test
+    public void testAutomaticOnOffKeepaliveModeClose() throws Exception {
+        doTestAutomaticOnOffKeepaliveMode(true);
+    }
+
+    private void startKeepalive(SocketKeepalive kp, TestSocketKeepaliveCallback callback) {
+        runWithShellPermissionIdentity(() -> {
+            // Only SocketKeepalive.start() requires READ_DEVICE_CONFIG because feature is protected
+            // by a feature flag. But also verify ON_STARTED callback received here to ensure
+            // keepalive is indeed started because start() runs in the executor thread and shell
+            // permission may be dropped before reading DeviceConfig.
+            kp.start(10 /* intervalSec */, SocketKeepalive.FLAG_AUTOMATIC_ON_OFF, mNetwork);
+
+            // Verify callback status.
+            assertEquals(ON_STARTED, callback.poll());
+        }, READ_DEVICE_CONFIG);
+    }
+
+    private void doTestAutomaticOnOffKeepaliveMode(final boolean closeSocket) throws Exception {
+        assumeTrue(supportedHardware());
+
+        // Get default network first before starting VPN
+        final Network defaultNetwork = mCM.getActiveNetwork();
+        final TestableNetworkCallback cb = new TestableNetworkCallback();
+        registerDefaultNetworkCallback(cb);
+        cb.expect(CallbackEntry.AVAILABLE, defaultNetwork);
+        final NetworkCapabilities cap =
+                cb.expect(CallbackEntry.NETWORK_CAPS_UPDATED, defaultNetwork).getCaps();
+        final LinkProperties lp =
+                cb.expect(CallbackEntry.LINK_PROPERTIES_CHANGED, defaultNetwork).getLp();
+        cb.expect(CallbackEntry.BLOCKED_STATUS, defaultNetwork);
+
+        // Setup VPN
+        final FileDescriptor fd = openSocketFdInOtherApp(TEST_HOST, 80, TIMEOUT_MS);
+        final String allowedApps = mRemoteSocketFactoryClient.getPackageName() + "," + mPackageName;
+        startVpn(new String[]{"192.0.2.2/32", "2001:db8:1:2::ffe/128"},
+                new String[]{"192.0.2.0/24", "2001:db8::/32"},
+                allowedApps, "" /* disallowedApplications */, null /* proxyInfo */,
+                null /* underlyingNetworks */, false /* isAlwaysMetered */);
+        assertSocketClosed(fd, TEST_HOST);
+
+        // Decrease the TCP polling timer for testing.
+        runWithShellPermissionIdentity(() -> mCM.setTestLowTcpPollingTimerForKeepalive(
+                System.currentTimeMillis() + TEST_TCP_POLLING_TIMER_EXPIRED_PERIOD_MS),
+                NETWORK_SETTINGS);
+
+        // Setup keepalive
+        final int supported = getSupportedKeepalives(cap);
+        assumeTrue("Network " + defaultNetwork + " does not support keepalive", supported != 0);
+        final InetAddress srcAddr = CollectionUtils.findFirst(lp.getAddresses(),
+                it -> it instanceof Inet4Address);
+        assumeTrue("This test requires native IPv4", srcAddr != null);
+
+        final TestSocketKeepaliveCallback callback = new TestSocketKeepaliveCallback();
+
+        final String origMode = runWithShellPermissionIdentity(() -> {
+            final String mode = DeviceConfig.getProperty(
+                    DeviceConfig.NAMESPACE_CONNECTIVITY, AUTOMATIC_ON_OFF_KEEPALIVE_VERSION);
+            DeviceConfig.setProperty(DeviceConfig.NAMESPACE_CONNECTIVITY,
+                    AUTOMATIC_ON_OFF_KEEPALIVE_VERSION,
+                    AUTOMATIC_ON_OFF_KEEPALIVE_ENABLED, false /* makeDefault */);
+            return mode;
+        }, READ_DEVICE_CONFIG, WRITE_DEVICE_CONFIG);
+
+        final IpSecManager ipSec = mTargetContext.getSystemService(IpSecManager.class);
+        SocketKeepalive kp = null;
+        try (IpSecManager.UdpEncapsulationSocket nattSocket = ipSec.openUdpEncapsulationSocket()) {
+            final InetAddress dstAddr = getV4AddrByName(TEST_HOST);
+            assertNotNull(dstAddr);
+
+            // Start keepalive with dynamic keepalive mode enabled.
+            final Executor executor = mTargetContext.getMainExecutor();
+            kp = mCM.createSocketKeepalive(defaultNetwork, nattSocket,
+                    srcAddr, dstAddr, executor, callback);
+            startKeepalive(kp, callback);
+
+            // There should be no open sockets on the VPN network, because any
+            // open sockets were closed when startVpn above was called. So the
+            // first TCP poll should trigger ON_PAUSED.
+            assertEquals(ON_PAUSED, callback.poll());
+
+            final Socket s = new Socket();
+            mNetwork.bindSocket(s);
+            s.connect(new InetSocketAddress(dstAddr, 80));
+            assertEquals(ON_RESUMED, callback.poll());
+
+            if (closeSocket) {
+                s.close();
+                assertEquals(ON_PAUSED, callback.poll());
+            }
+
+            kp.stop();
+            assertEquals(ON_STOPPED, callback.poll());
+        } finally {
+            if (kp != null) kp.stop();
+
+            runWithShellPermissionIdentity(() -> {
+                DeviceConfig.setProperty(
+                                DeviceConfig.NAMESPACE_CONNECTIVITY,
+                                AUTOMATIC_ON_OFF_KEEPALIVE_VERSION,
+                                origMode, false);
+                mCM.setTestLowTcpPollingTimerForKeepalive(0);
+            }, WRITE_DEVICE_CONFIG, NETWORK_SETTINGS);
+        }
+    }
+
     @Test
     public void testAppDisallowed() throws Exception {
         assumeTrue(supportedHardware());
@@ -1623,7 +1838,7 @@
 
         testAndCleanup(() -> {
             runWithShellPermissionIdentity(() -> {
-                mCM.registerDefaultNetworkCallbackForUid(remoteUid, remoteUidCallback,
+                registerDefaultNetworkCallbackForUid(remoteUid, remoteUidCallback,
                         new Handler(Looper.getMainLooper()));
             }, NETWORK_SETTINGS);
             remoteUidCallback.expectAvailableCallbacksWithBlockedReasonNone(network);
@@ -1659,8 +1874,6 @@
 
             checkBlockIncomingPacket(tunFd, remoteUdpFd, EXPECT_BLOCK);
         }, /* cleanup */ () -> {
-                mCM.unregisterNetworkCallback(remoteUidCallback);
-            }, /* cleanup */ () -> {
                 Os.close(tunFd);
             }, /* cleanup */ () -> {
                 Os.close(remoteUdpFd);
@@ -1684,7 +1897,7 @@
         final int myUid = Process.myUid();
 
         testAndCleanup(() -> {
-            mCM.registerDefaultNetworkCallback(defaultNetworkCallback);
+            registerDefaultNetworkCallback(defaultNetworkCallback);
             defaultNetworkCallback.expectAvailableCallbacks(defaultNetwork);
 
             final Range<Integer> myUidRange = new Range<>(myUid, myUid);
@@ -1716,8 +1929,6 @@
                 defaultNetworkCallback.eventuallyExpect(CallbackEntry.AVAILABLE,
                         NETWORK_CALLBACK_TIMEOUT_MS,
                         entry -> defaultNetwork.equals(entry.getNetwork()));
-            }, /* cleanup */ () -> {
-                mCM.unregisterNetworkCallback(defaultNetworkCallback);
             });
     }
 
diff --git a/tests/cts/hostside/networkslicingtestapp/Android.bp b/tests/cts/hostside/networkslicingtestapp/Android.bp
new file mode 100644
index 0000000..2aa3f69
--- /dev/null
+++ b/tests/cts/hostside/networkslicingtestapp/Android.bp
@@ -0,0 +1,65 @@
+//
+// 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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+java_defaults {
+    name: "CtsHostsideNetworkCapTestsAppDefaults",
+    platform_apis: true,
+    static_libs: [
+        "androidx.test.ext.junit",
+        "androidx.test.rules",
+        "modules-utils-build",
+        "cts-net-utils",
+    ],
+    srcs: ["src/**/*.java"],
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+        "sts",
+    ],
+}
+
+android_test_helper_app {
+    name: "CtsHostsideNetworkCapTestsAppWithoutProperty",
+    defaults: [
+           "cts_support_defaults",
+           "CtsHostsideNetworkCapTestsAppDefaults"
+    ],
+    manifest: "AndroidManifestWithoutProperty.xml",
+}
+
+android_test_helper_app {
+    name: "CtsHostsideNetworkCapTestsAppWithProperty",
+    defaults: [
+           "cts_support_defaults",
+           "CtsHostsideNetworkCapTestsAppDefaults"
+    ],
+    manifest: "AndroidManifestWithProperty.xml",
+}
+
+android_test_helper_app {
+    name: "CtsHostsideNetworkCapTestsAppSdk33",
+    defaults: [
+           "cts_support_defaults",
+           "CtsHostsideNetworkCapTestsAppDefaults"
+    ],
+    target_sdk_version: "33",
+    manifest: "AndroidManifestWithoutProperty.xml",
+}
diff --git a/tests/cts/hostside/networkslicingtestapp/AndroidManifestWithProperty.xml b/tests/cts/hostside/networkslicingtestapp/AndroidManifestWithProperty.xml
new file mode 100644
index 0000000..3ef0376
--- /dev/null
+++ b/tests/cts/hostside/networkslicingtestapp/AndroidManifestWithProperty.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.cts.net.hostside.networkslicingtestapp">
+
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+    <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
+
+    <application>
+        <uses-library android:name="android.test.runner"/>
+        <property android:name="android.net.PROPERTY_SELF_CERTIFIED_NETWORK_CAPABILITIES"
+                  android:resource="@xml/self_certified_network_capabilities_both" />
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.cts.net.hostside.networkslicingtestapp"/>
+
+</manifest>
diff --git a/tests/cts/hostside/networkslicingtestapp/AndroidManifestWithoutProperty.xml b/tests/cts/hostside/networkslicingtestapp/AndroidManifestWithoutProperty.xml
new file mode 100644
index 0000000..fe66684
--- /dev/null
+++ b/tests/cts/hostside/networkslicingtestapp/AndroidManifestWithoutProperty.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.cts.net.hostside.networkslicingtestapp">
+
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+    <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
+
+    <application>
+        <uses-library android:name="android.test.runner"/>
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.cts.net.hostside.networkslicingtestapp"/>
+
+</manifest>
diff --git a/tests/cts/hostside/networkslicingtestapp/res/xml/self_certified_network_capabilities_both.xml b/tests/cts/hostside/networkslicingtestapp/res/xml/self_certified_network_capabilities_both.xml
new file mode 100644
index 0000000..4066be2
--- /dev/null
+++ b/tests/cts/hostside/networkslicingtestapp/res/xml/self_certified_network_capabilities_both.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<network-capabilities-declaration xmlns:android="http://schemas.android.com/apk/res/android">
+    <uses-network-capability android:name="NET_CAPABILITY_PRIORITIZE_LATENCY"/>
+    <uses-network-capability android:name="NET_CAPABILITY_PRIORITIZE_BANDWIDTH"/>
+</network-capabilities-declaration>
diff --git a/tests/cts/hostside/networkslicingtestapp/src/com.android.cts.net.hostside.networkslicingtestapp/NetworkSelfDeclaredCapabilitiesTest.java b/tests/cts/hostside/networkslicingtestapp/src/com.android.cts.net.hostside.networkslicingtestapp/NetworkSelfDeclaredCapabilitiesTest.java
new file mode 100644
index 0000000..39792fc
--- /dev/null
+++ b/tests/cts/hostside/networkslicingtestapp/src/com.android.cts.net.hostside.networkslicingtestapp/NetworkSelfDeclaredCapabilitiesTest.java
@@ -0,0 +1,98 @@
+/*
+ * 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 com.android.cts.net.hostside.networkslicingtestapp;
+
+import static org.junit.Assert.assertThrows;
+
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
+import android.os.Build;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class NetworkSelfDeclaredCapabilitiesTest {
+
+    @Rule
+    public final DevSdkIgnoreRule mDevSdkIgnoreRule = new DevSdkIgnoreRule();
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+    public void requestNetwork_withoutRequestCapabilities() {
+        final ConnectivityManager cm =
+                (ConnectivityManager)
+                        InstrumentationRegistry.getInstrumentation()
+                                .getContext()
+                                .getSystemService(Context.CONNECTIVITY_SERVICE);
+        final NetworkRequest request =
+                new NetworkRequest.Builder().build();
+        final ConnectivityManager.NetworkCallback callback =
+                new ConnectivityManager.NetworkCallback();
+        cm.requestNetwork(request, callback);
+        cm.unregisterNetworkCallback(callback);
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+    public void requestNetwork_withSelfDeclaredCapabilities() {
+        final ConnectivityManager cm =
+                (ConnectivityManager)
+                        InstrumentationRegistry.getInstrumentation()
+                                .getContext()
+                                .getSystemService(Context.CONNECTIVITY_SERVICE);
+        final NetworkRequest request =
+                new NetworkRequest.Builder()
+                        .addCapability(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_BANDWIDTH)
+                        .addCapability(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_LATENCY)
+                        .build();
+        final ConnectivityManager.NetworkCallback callback =
+                new ConnectivityManager.NetworkCallback();
+        cm.requestNetwork(request, callback);
+        cm.unregisterNetworkCallback(callback);
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+    public void requestNetwork_lackingRequiredSelfDeclaredCapabilities() {
+        final ConnectivityManager cm =
+                (ConnectivityManager)
+                        InstrumentationRegistry.getInstrumentation()
+                                .getContext()
+                                .getSystemService(Context.CONNECTIVITY_SERVICE);
+        final NetworkRequest request =
+                new NetworkRequest.Builder()
+                        .addCapability(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_BANDWIDTH)
+                        .addCapability(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_LATENCY)
+                        .build();
+        final ConnectivityManager.NetworkCallback callback =
+                new ConnectivityManager.NetworkCallback();
+        assertThrows(
+                SecurityException.class,
+                () -> cm.requestNetwork(request, callback));
+    }
+
+}
diff --git a/tests/cts/hostside/src/com/android/cts/net/HostsideSelfDeclaredNetworkCapabilitiesCheckTest.java b/tests/cts/hostside/src/com/android/cts/net/HostsideSelfDeclaredNetworkCapabilitiesCheckTest.java
new file mode 100644
index 0000000..4c2985d
--- /dev/null
+++ b/tests/cts/hostside/src/com/android/cts/net/HostsideSelfDeclaredNetworkCapabilitiesCheckTest.java
@@ -0,0 +1,96 @@
+/*
+ * 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 com.android.cts.net;
+
+public class HostsideSelfDeclaredNetworkCapabilitiesCheckTest extends HostsideNetworkTestCase {
+
+    private static final String TEST_WITH_PROPERTY_IN_CURRENT_SDK_APK =
+            "CtsHostsideNetworkCapTestsAppWithProperty.apk";
+    private static final String TEST_WITHOUT_PROPERTY_IN_CURRENT_SDK_APK =
+            "CtsHostsideNetworkCapTestsAppWithoutProperty.apk";
+    private static final String TEST_IN_SDK_33_APK =
+            "CtsHostsideNetworkCapTestsAppSdk33.apk";
+    private static final String TEST_APP_PKG =
+            "com.android.cts.net.hostside.networkslicingtestapp";
+    private static final String TEST_CLASS_NAME = ".NetworkSelfDeclaredCapabilitiesTest";
+    private static final String WITH_SELF_DECLARED_CAPABILITIES_METHOD =
+            "requestNetwork_withSelfDeclaredCapabilities";
+    private static final String LACKING_SELF_DECLARED_CAPABILITIES_METHOD =
+            "requestNetwork_lackingRequiredSelfDeclaredCapabilities";
+    private static final String WITHOUT_REQUEST_CAPABILITIES_METHOD =
+            "requestNetwork_withoutRequestCapabilities";
+
+
+    public void testRequestNetworkInCurrentSdkWithProperty() throws Exception {
+        uninstallPackage(TEST_APP_PKG, false);
+        installPackage(TEST_WITH_PROPERTY_IN_CURRENT_SDK_APK);
+        // If the self-declared capabilities are defined,
+        // the ConnectivityManager.requestNetwork() call should always pass.
+        runDeviceTests(TEST_APP_PKG,
+                TEST_APP_PKG + TEST_CLASS_NAME,
+                WITH_SELF_DECLARED_CAPABILITIES_METHOD);
+        runDeviceTests(TEST_APP_PKG,
+                TEST_APP_PKG + TEST_CLASS_NAME,
+                WITHOUT_REQUEST_CAPABILITIES_METHOD);
+        uninstallPackage(TEST_APP_PKG, true);
+    }
+
+    public void testRequestNetworkInCurrentSdkWithoutProperty() throws Exception {
+        uninstallPackage(TEST_APP_PKG, false);
+        installPackage(TEST_WITHOUT_PROPERTY_IN_CURRENT_SDK_APK);
+        // If the self-declared capabilities are not defined,
+        // the ConnectivityManager.requestNetwork() call will fail if the properly is not declared.
+        runDeviceTests(TEST_APP_PKG,
+                TEST_APP_PKG + TEST_CLASS_NAME,
+                LACKING_SELF_DECLARED_CAPABILITIES_METHOD);
+        runDeviceTests(TEST_APP_PKG,
+                TEST_APP_PKG + TEST_CLASS_NAME,
+                WITHOUT_REQUEST_CAPABILITIES_METHOD);
+        uninstallPackage(TEST_APP_PKG, true);
+    }
+
+    public void testRequestNetworkInSdk33() throws Exception {
+        uninstallPackage(TEST_APP_PKG, false);
+        installPackage(TEST_IN_SDK_33_APK);
+        // In Sdk33, the ConnectivityManager.requestNetwork() call should always pass.
+        runDeviceTests(TEST_APP_PKG,
+                TEST_APP_PKG + TEST_CLASS_NAME,
+                WITH_SELF_DECLARED_CAPABILITIES_METHOD);
+        runDeviceTests(TEST_APP_PKG,
+                TEST_APP_PKG + TEST_CLASS_NAME,
+                WITHOUT_REQUEST_CAPABILITIES_METHOD);
+        uninstallPackage(TEST_APP_PKG, true);
+    }
+
+    public void testReinstallPackageWillUpdateProperty() throws Exception {
+        uninstallPackage(TEST_APP_PKG, false);
+        installPackage(TEST_WITHOUT_PROPERTY_IN_CURRENT_SDK_APK);
+        runDeviceTests(TEST_APP_PKG,
+                TEST_APP_PKG + TEST_CLASS_NAME,
+                LACKING_SELF_DECLARED_CAPABILITIES_METHOD);
+        uninstallPackage(TEST_APP_PKG, true);
+
+
+        // Updates package.
+        installPackage(TEST_WITH_PROPERTY_IN_CURRENT_SDK_APK);
+        runDeviceTests(TEST_APP_PKG,
+                TEST_APP_PKG + TEST_CLASS_NAME,
+                WITH_SELF_DECLARED_CAPABILITIES_METHOD);
+        uninstallPackage(TEST_APP_PKG, true);
+
+    }
+}
+
diff --git a/tests/cts/hostside/src/com/android/cts/net/HostsideVpnTests.java b/tests/cts/hostside/src/com/android/cts/net/HostsideVpnTests.java
index 10a2821..603779d 100644
--- a/tests/cts/hostside/src/com/android/cts/net/HostsideVpnTests.java
+++ b/tests/cts/hostside/src/com/android/cts/net/HostsideVpnTests.java
@@ -16,6 +16,8 @@
 
 package com.android.cts.net;
 
+import android.platform.test.annotations.RequiresDevice;
+
 public class HostsideVpnTests extends HostsideNetworkTestCase {
 
     @Override
@@ -89,6 +91,18 @@
                 TEST_PKG, TEST_PKG + ".VpnTest", "testAlwaysMeteredVpnWithNullUnderlyingNetwork");
     }
 
+    @RequiresDevice // Keepalive is not supported on virtual hardware
+    public void testAutomaticOnOffKeepaliveModeClose() throws Exception {
+        runDeviceTests(
+                TEST_PKG, TEST_PKG + ".VpnTest", "testAutomaticOnOffKeepaliveModeClose");
+    }
+
+    @RequiresDevice // Keepalive is not supported on virtual hardware
+    public void testAutomaticOnOffKeepaliveModeNoClose() throws Exception {
+        runDeviceTests(
+                TEST_PKG, TEST_PKG + ".VpnTest", "testAutomaticOnOffKeepaliveModeNoClose");
+    }
+
     public void testAlwaysMeteredVpnWithNonNullUnderlyingNetwork() throws Exception {
         runDeviceTests(
                 TEST_PKG,
diff --git a/tests/cts/net/Android.bp b/tests/cts/net/Android.bp
index 23cb15c..f9fe5b0 100644
--- a/tests/cts/net/Android.bp
+++ b/tests/cts/net/Android.bp
@@ -114,34 +114,39 @@
     ],
 }
 
-android_test {
-    name: "CtsNetTestCasesMaxTargetSdk31",  // Must match CtsNetTestCasesMaxTargetSdk31 annotation.
+java_defaults {
+    name: "CtsNetTestCasesMaxTargetSdkDefaults",
     defaults: [
         "CtsNetTestCasesDefaults",
         "CtsNetTestCasesApiStableDefaults",
     ],
-    target_sdk_version: "31",
-    package_name: "android.net.cts.maxtargetsdk31",  // CTS package names must be unique.
-    instrumentation_target_package: "android.net.cts.maxtargetsdk31",
     test_suites: [
         "cts",
         "general-tests",
-        "mts-networking",
+        "mts-tethering",
     ],
 }
 
 android_test {
+    name: "CtsNetTestCasesMaxTargetSdk33",  // Must match CtsNetTestCasesMaxTargetSdk33 annotation.
+    defaults: ["CtsNetTestCasesMaxTargetSdkDefaults"],
+    target_sdk_version: "33",
+    package_name: "android.net.cts.maxtargetsdk33",
+    instrumentation_target_package: "android.net.cts.maxtargetsdk33",
+}
+
+android_test {
+    name: "CtsNetTestCasesMaxTargetSdk31",  // Must match CtsNetTestCasesMaxTargetSdk31 annotation.
+    defaults: ["CtsNetTestCasesMaxTargetSdkDefaults"],
+    target_sdk_version: "31",
+    package_name: "android.net.cts.maxtargetsdk31",  // CTS package names must be unique.
+    instrumentation_target_package: "android.net.cts.maxtargetsdk31",
+}
+
+android_test {
     name: "CtsNetTestCasesMaxTargetSdk30",  // Must match CtsNetTestCasesMaxTargetSdk30 annotation.
-    defaults: [
-        "CtsNetTestCasesDefaults",
-        "CtsNetTestCasesApiStableDefaults",
-    ],
+    defaults: ["CtsNetTestCasesMaxTargetSdkDefaults"],
     target_sdk_version: "30",
     package_name: "android.net.cts.maxtargetsdk30",  // CTS package names must be unique.
     instrumentation_target_package: "android.net.cts.maxtargetsdk30",
-    test_suites: [
-        "cts",
-        "general-tests",
-        "mts-networking",
-    ],
 }
diff --git a/tests/cts/net/src/android/net/cts/CaptivePortalTest.kt b/tests/cts/net/src/android/net/cts/CaptivePortalTest.kt
index 7c24c95..dc22369 100644
--- a/tests/cts/net/src/android/net/cts/CaptivePortalTest.kt
+++ b/tests/cts/net/src/android/net/cts/CaptivePortalTest.kt
@@ -37,8 +37,6 @@
 import android.net.cts.NetworkValidationTestUtil.setHttpsUrlDeviceConfig
 import android.net.cts.NetworkValidationTestUtil.setUrlExpirationDeviceConfig
 import android.net.cts.util.CtsNetUtils
-import com.android.net.module.util.NetworkStackConstants.TEST_CAPTIVE_PORTAL_HTTPS_URL
-import com.android.net.module.util.NetworkStackConstants.TEST_CAPTIVE_PORTAL_HTTP_URL
 import android.platform.test.annotations.AppModeFull
 import android.provider.DeviceConfig
 import android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY
@@ -47,28 +45,30 @@
 import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
 import androidx.test.runner.AndroidJUnit4
 import com.android.modules.utils.build.SdkLevel.isAtLeastR
+import com.android.net.module.util.NetworkStackConstants.TEST_CAPTIVE_PORTAL_HTTPS_URL
+import com.android.net.module.util.NetworkStackConstants.TEST_CAPTIVE_PORTAL_HTTP_URL
 import com.android.testutils.DeviceConfigRule
-import com.android.testutils.RecorderCallback
+import com.android.testutils.RecorderCallback.CallbackEntry.CapabilitiesChanged
 import com.android.testutils.TestHttpServer
 import com.android.testutils.TestHttpServer.Request
 import com.android.testutils.TestableNetworkCallback
 import com.android.testutils.runAsShell
 import fi.iki.elonen.NanoHTTPD.Response.Status
-import junit.framework.AssertionFailedError
-import org.junit.After
-import org.junit.Assume.assumeTrue
-import org.junit.Assume.assumeFalse
-import org.junit.Before
-import org.junit.BeforeClass
-import org.junit.Rule
-import org.junit.runner.RunWith
 import java.util.concurrent.CompletableFuture
 import java.util.concurrent.TimeUnit
 import java.util.concurrent.TimeoutException
+import junit.framework.AssertionFailedError
 import kotlin.test.Test
 import kotlin.test.assertNotEquals
 import kotlin.test.assertNotNull
 import kotlin.test.assertTrue
+import org.junit.After
+import org.junit.Assume.assumeFalse
+import org.junit.Assume.assumeTrue
+import org.junit.Before
+import org.junit.BeforeClass
+import org.junit.Rule
+import org.junit.runner.RunWith
 
 private const val TEST_HTTPS_URL_PATH = "/https_path"
 private const val TEST_HTTP_URL_PATH = "/http_path"
@@ -151,8 +151,8 @@
                 .build()
         val cellCb = TestableNetworkCallback(timeoutMs = TEST_TIMEOUT_MS)
         cm.registerNetworkCallback(cellReq, cellCb)
-        val cb = cellCb.eventuallyExpectOrNull<RecorderCallback.CallbackEntry.CapabilitiesChanged> {
-            it.network == cellNetwork && it.caps.hasCapability(NET_CAPABILITY_VALIDATED)
+        val cb = cellCb.poll { it.network == cellNetwork &&
+                it is CapabilitiesChanged && it.caps.hasCapability(NET_CAPABILITY_VALIDATED)
         }
         assertNotNull(cb, "Mobile network $cellNetwork has no access to the internet. " +
                 "Check the mobile data connection.")
diff --git a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
index 7985dc4..774176f 100644
--- a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
@@ -2434,11 +2434,11 @@
         runWithShellPermissionIdentity(() -> registerDefaultNetworkCallbackForUid(
                 otherUid, otherUidCallback, handler), NETWORK_SETTINGS);
 
-        final Network defaultNetwork = mCm.getActiveNetwork();
+        final Network defaultNetwork = myUidCallback.expect(CallbackEntry.AVAILABLE).getNetwork();
         final List<DetailedBlockedStatusCallback> allCallbacks =
                 List.of(myUidCallback, otherUidCallback);
         for (DetailedBlockedStatusCallback callback : allCallbacks) {
-            callback.expectAvailableCallbacksWithBlockedReasonNone(defaultNetwork);
+            callback.eventuallyExpectBlockedStatusCallback(defaultNetwork, BLOCKED_REASON_NONE);
         }
 
         final Range<Integer> myUidRange = new Range<>(myUid, myUid);
@@ -2708,6 +2708,7 @@
         // Cannot use @IgnoreUpTo(Build.VERSION_CODES.R) because this test also requires API 31
         // shims, and @IgnoreUpTo does not check that.
         assumeTrue(TestUtils.shouldTestSApis());
+        assumeTrue(mPackageManager.hasSystemFeature(FEATURE_WIFI));
 
         final TestNetworkTracker tnt = callWithShellPermissionIdentity(
                 () -> initTestNetwork(mContext, TEST_LINKADDR, NETWORK_CALLBACK_TIMEOUT_MS));
@@ -2721,7 +2722,8 @@
                     OemNetworkPreferences.OEM_NETWORK_PREFERENCE_TEST_ONLY);
             registerTestOemNetworkPreferenceCallbacks(defaultCallback, systemDefaultCallback);
             waitForAvailable(defaultCallback, tnt.getNetwork());
-            waitForAvailable(systemDefaultCallback, wifiNetwork);
+            systemDefaultCallback.eventuallyExpect(CallbackEntry.AVAILABLE,
+                    NETWORK_CALLBACK_TIMEOUT_MS, cb -> wifiNetwork.equals(cb.getNetwork()));
         }, /* cleanup */ () -> {
                 runWithShellPermissionIdentity(tnt::teardown);
                 defaultCallback.expect(CallbackEntry.LOST, tnt, NETWORK_CALLBACK_TIMEOUT_MS);
@@ -3406,6 +3408,9 @@
 
     private static final boolean EXPECT_PASS = false;
     private static final boolean EXPECT_BLOCK = true;
+
+    // ALLOWLIST means the firewall denies all by default, uids must be explicitly allowed
+    // DENYLIST means the firewall allows all by default, uids must be explicitly denyed
     private static final boolean ALLOWLIST = true;
     private static final boolean DENYLIST = false;
 
@@ -3471,17 +3476,49 @@
 
     @Test @IgnoreUpTo(SC_V2) @ConnectivityModuleTest
     @AppModeFull(reason = "Socket cannot bind in instant app mode")
-    public void testFirewallBlocking() {
-        // ALLOWLIST means the firewall denies all by default, uids must be explicitly allowed
+    public void testFirewallBlockingDozable() {
         doTestFirewallBlocking(FIREWALL_CHAIN_DOZABLE, ALLOWLIST);
-        doTestFirewallBlocking(FIREWALL_CHAIN_POWERSAVE, ALLOWLIST);
-        doTestFirewallBlocking(FIREWALL_CHAIN_RESTRICTED, ALLOWLIST);
-        doTestFirewallBlocking(FIREWALL_CHAIN_LOW_POWER_STANDBY, ALLOWLIST);
+    }
 
-        // DENYLIST means the firewall allows all by default, uids must be explicitly denyed
+    @Test @IgnoreUpTo(SC_V2) @ConnectivityModuleTest
+    @AppModeFull(reason = "Socket cannot bind in instant app mode")
+    public void testFirewallBlockingPowersave() {
+        doTestFirewallBlocking(FIREWALL_CHAIN_POWERSAVE, ALLOWLIST);
+    }
+
+    @Test @IgnoreUpTo(SC_V2) @ConnectivityModuleTest
+    @AppModeFull(reason = "Socket cannot bind in instant app mode")
+    public void testFirewallBlockingRestricted() {
+        doTestFirewallBlocking(FIREWALL_CHAIN_RESTRICTED, ALLOWLIST);
+    }
+
+    @Test @IgnoreUpTo(SC_V2) @ConnectivityModuleTest
+    @AppModeFull(reason = "Socket cannot bind in instant app mode")
+    public void testFirewallBlockingLowPowerStandby() {
+        doTestFirewallBlocking(FIREWALL_CHAIN_LOW_POWER_STANDBY, ALLOWLIST);
+    }
+
+    @Test @IgnoreUpTo(SC_V2) @ConnectivityModuleTest
+    @AppModeFull(reason = "Socket cannot bind in instant app mode")
+    public void testFirewallBlockingStandby() {
         doTestFirewallBlocking(FIREWALL_CHAIN_STANDBY, DENYLIST);
+    }
+
+    @Test @IgnoreUpTo(SC_V2) @ConnectivityModuleTest
+    @AppModeFull(reason = "Socket cannot bind in instant app mode")
+    public void testFirewallBlockingOemDeny1() {
         doTestFirewallBlocking(FIREWALL_CHAIN_OEM_DENY_1, DENYLIST);
+    }
+
+    @Test @IgnoreUpTo(SC_V2) @ConnectivityModuleTest
+    @AppModeFull(reason = "Socket cannot bind in instant app mode")
+    public void testFirewallBlockingOemDeny2() {
         doTestFirewallBlocking(FIREWALL_CHAIN_OEM_DENY_2, DENYLIST);
+    }
+
+    @Test @IgnoreUpTo(SC_V2) @ConnectivityModuleTest
+    @AppModeFull(reason = "Socket cannot bind in instant app mode")
+    public void testFirewallBlockingOemDeny3() {
         doTestFirewallBlocking(FIREWALL_CHAIN_OEM_DENY_3, DENYLIST);
     }
 
diff --git a/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt b/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt
index b924f65..67bdd17 100644
--- a/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt
+++ b/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt
@@ -644,10 +644,9 @@
 
         val listener = EthernetStateListener()
         addInterfaceStateListener(listener)
-        // TODO(b/236895792): THIS IS A BUG! Existing server mode interfaces are not reported when
-        // an InterfaceStateListener is registered.
         // Note: using eventuallyExpect as there may be other interfaces present.
-        // listener.eventuallyExpect(iface, STATE_LINK_UP, ROLE_SERVER)
+        listener.eventuallyExpect(InterfaceStateChanged(iface.name,
+                STATE_LINK_UP, ROLE_SERVER, /* IpConfiguration */ null))
 
         releaseTetheredInterface()
         listener.eventuallyExpect(iface, STATE_LINK_UP, ROLE_CLIENT)
diff --git a/tests/cts/net/src/android/net/cts/Ikev2VpnTest.java b/tests/cts/net/src/android/net/cts/Ikev2VpnTest.java
index 6ba0fda..805dd65 100644
--- a/tests/cts/net/src/android/net/cts/Ikev2VpnTest.java
+++ b/tests/cts/net/src/android/net/cts/Ikev2VpnTest.java
@@ -60,7 +60,11 @@
 
 import com.android.internal.util.HexDump;
 import com.android.networkstack.apishim.ConstantsShim;
+import com.android.networkstack.apishim.Ikev2VpnProfileBuilderShimImpl;
+import com.android.networkstack.apishim.Ikev2VpnProfileShimImpl;
 import com.android.networkstack.apishim.VpnManagerShimImpl;
+import com.android.networkstack.apishim.common.Ikev2VpnProfileBuilderShim;
+import com.android.networkstack.apishim.common.Ikev2VpnProfileShim;
 import com.android.networkstack.apishim.common.VpnManagerShim;
 import com.android.networkstack.apishim.common.VpnProfileStateShim;
 import com.android.testutils.DevSdkIgnoreRule;
@@ -223,17 +227,28 @@
     }
 
     private Ikev2VpnProfile buildIkev2VpnProfileCommon(
-            @NonNull Ikev2VpnProfile.Builder builder, boolean isRestrictedToTestNetworks,
-            boolean requiresValidation) throws Exception {
+            @NonNull Ikev2VpnProfileBuilderShim builderShim, boolean isRestrictedToTestNetworks,
+            boolean requiresValidation, boolean automaticIpVersionSelectionEnabled,
+            boolean automaticNattKeepaliveTimerEnabled) throws Exception {
 
-        builder.setBypassable(true)
+        builderShim.setBypassable(true)
                 .setAllowedAlgorithms(TEST_ALLOWED_ALGORITHMS)
                 .setProxy(TEST_PROXY_INFO)
                 .setMaxMtu(TEST_MTU)
                 .setMetered(false);
         if (TestUtils.shouldTestTApis()) {
-            builder.setRequiresInternetValidation(requiresValidation);
+            builderShim.setRequiresInternetValidation(requiresValidation);
         }
+
+        if (TestUtils.shouldTestUApis()) {
+            builderShim.setAutomaticIpVersionSelectionEnabled(automaticIpVersionSelectionEnabled);
+            builderShim.setAutomaticNattKeepaliveTimerEnabled(automaticNattKeepaliveTimerEnabled);
+        }
+
+        // Convert shim back to Ikev2VpnProfile.Builder since restrictToTestNetworks is a hidden
+        // method and is not defined in shims.
+        // TODO: replace it in alternative way to remove the hidden method usage
+        final Ikev2VpnProfile.Builder builder = (Ikev2VpnProfile.Builder) builderShim.getBuilder();
         if (isRestrictedToTestNetworks) {
             builder.restrictToTestNetworks();
         }
@@ -249,13 +264,16 @@
                         ? IkeSessionTestUtils.IKE_PARAMS_V6 : IkeSessionTestUtils.IKE_PARAMS_V4,
                         IkeSessionTestUtils.CHILD_PARAMS);
 
-        final Ikev2VpnProfile.Builder builder =
-                new Ikev2VpnProfile.Builder(params)
+        final Ikev2VpnProfileBuilderShim builderShim =
+                Ikev2VpnProfileBuilderShimImpl.newInstance(params)
                         .setRequiresInternetValidation(requiresValidation)
                         .setProxy(TEST_PROXY_INFO)
                         .setMaxMtu(TEST_MTU)
                         .setMetered(false);
-
+        // Convert shim back to Ikev2VpnProfile.Builder since restrictToTestNetworks is a hidden
+        // method and is not defined in shims.
+        // TODO: replace it in alternative way to remove the hidden method usage
+        final Ikev2VpnProfile.Builder builder = (Ikev2VpnProfile.Builder) builderShim.getBuilder();
         if (isRestrictedToTestNetworks) {
             builder.restrictToTestNetworks();
         }
@@ -263,31 +281,35 @@
     }
 
     private Ikev2VpnProfile buildIkev2VpnProfilePsk(@NonNull String remote,
-            boolean isRestrictedToTestNetworks, boolean requiresValidation) throws Exception {
-        final Ikev2VpnProfile.Builder builder =
-                new Ikev2VpnProfile.Builder(remote, TEST_IDENTITY).setAuthPsk(TEST_PSK);
+            boolean isRestrictedToTestNetworks, boolean requiresValidation)
+            throws Exception {
+        final Ikev2VpnProfileBuilderShim builder =
+                Ikev2VpnProfileBuilderShimImpl.newInstance(remote, TEST_IDENTITY)
+                        .setAuthPsk(TEST_PSK);
         return buildIkev2VpnProfileCommon(builder, isRestrictedToTestNetworks,
-                requiresValidation);
+                requiresValidation, false /* automaticIpVersionSelectionEnabled */,
+                false /* automaticNattKeepaliveTimerEnabled */);
     }
 
     private Ikev2VpnProfile buildIkev2VpnProfileUsernamePassword(boolean isRestrictedToTestNetworks)
             throws Exception {
-
-        final Ikev2VpnProfile.Builder builder =
-                new Ikev2VpnProfile.Builder(TEST_SERVER_ADDR_V6, TEST_IDENTITY)
+        final Ikev2VpnProfileBuilderShim builder =
+                Ikev2VpnProfileBuilderShimImpl.newInstance(TEST_SERVER_ADDR_V6, TEST_IDENTITY)
                         .setAuthUsernamePassword(TEST_USER, TEST_PASSWORD, mServerRootCa);
         return buildIkev2VpnProfileCommon(builder, isRestrictedToTestNetworks,
-                false /* requiresValidation */);
+                false /* requiresValidation */, false /* automaticIpVersionSelectionEnabled */,
+                false /* automaticNattKeepaliveTimerEnabled */);
     }
 
     private Ikev2VpnProfile buildIkev2VpnProfileDigitalSignature(boolean isRestrictedToTestNetworks)
             throws Exception {
-        final Ikev2VpnProfile.Builder builder =
-                new Ikev2VpnProfile.Builder(TEST_SERVER_ADDR_V6, TEST_IDENTITY)
+        final Ikev2VpnProfileBuilderShim builder =
+                Ikev2VpnProfileBuilderShimImpl.newInstance(TEST_SERVER_ADDR_V6, TEST_IDENTITY)
                         .setAuthDigitalSignature(
                                 mUserCertKey.cert, mUserCertKey.key, mServerRootCa);
         return buildIkev2VpnProfileCommon(builder, isRestrictedToTestNetworks,
-                false /* requiresValidation */);
+                false /* requiresValidation */, false /* automaticIpVersionSelectionEnabled */,
+                false /* automaticNattKeepaliveTimerEnabled */);
     }
 
     private void checkBasicIkev2VpnProfile(@NonNull Ikev2VpnProfile profile) throws Exception {
@@ -687,6 +709,56 @@
                 true /* testSessionKey */, false /* testIkeTunConnParams */);
     }
 
+    @Test
+    public void testBuildIkev2VpnProfileWithAutomaticNattKeepaliveTimerEnabled() throws Exception {
+        // Cannot use @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU) because this test also requires API
+        // 34 shims, and @IgnoreUpTo does not check that.
+        assumeTrue(TestUtils.shouldTestUApis());
+
+        final Ikev2VpnProfile profileWithDefaultValue = buildIkev2VpnProfilePsk(TEST_SERVER_ADDR_V6,
+                false /* isRestrictedToTestNetworks */, false /* requiresValidation */);
+        final Ikev2VpnProfileShim<Ikev2VpnProfile> shimWithDefaultValue =
+                Ikev2VpnProfileShimImpl.newInstance(profileWithDefaultValue);
+        assertFalse(shimWithDefaultValue.isAutomaticNattKeepaliveTimerEnabled());
+
+        final Ikev2VpnProfileBuilderShim builder =
+                Ikev2VpnProfileBuilderShimImpl.newInstance(TEST_SERVER_ADDR_V6, TEST_IDENTITY)
+                        .setAuthPsk(TEST_PSK);
+        final Ikev2VpnProfile profile = buildIkev2VpnProfileCommon(builder,
+                false /* isRestrictedToTestNetworks */,
+                false /* requiresValidation */,
+                false /* automaticIpVersionSelectionEnabled */,
+                true /* automaticNattKeepaliveTimerEnabled */);
+        final Ikev2VpnProfileShim<Ikev2VpnProfile> shim =
+                Ikev2VpnProfileShimImpl.newInstance(profile);
+        assertTrue(shim.isAutomaticNattKeepaliveTimerEnabled());
+    }
+
+    @Test
+    public void testBuildIkev2VpnProfileWithAutomaticIpVersionSelectionEnabled() throws Exception {
+        // Cannot use @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU) because this test also requires API
+        // 34 shims, and @IgnoreUpTo does not check that.
+        assumeTrue(TestUtils.shouldTestUApis());
+
+        final Ikev2VpnProfile profileWithDefaultValue = buildIkev2VpnProfilePsk(TEST_SERVER_ADDR_V6,
+                false /* isRestrictedToTestNetworks */, false /* requiresValidation */);
+        final Ikev2VpnProfileShim<Ikev2VpnProfile> shimWithDefaultValue =
+                Ikev2VpnProfileShimImpl.newInstance(profileWithDefaultValue);
+        assertFalse(shimWithDefaultValue.isAutomaticIpVersionSelectionEnabled());
+
+        final Ikev2VpnProfileBuilderShim builder =
+                Ikev2VpnProfileBuilderShimImpl.newInstance(TEST_SERVER_ADDR_V6, TEST_IDENTITY)
+                        .setAuthPsk(TEST_PSK);
+        final Ikev2VpnProfile profile = buildIkev2VpnProfileCommon(builder,
+                false /* isRestrictedToTestNetworks */,
+                false /* requiresValidation */,
+                true /* automaticIpVersionSelectionEnabled */,
+                false /* automaticNattKeepaliveTimerEnabled */);
+        final Ikev2VpnProfileShim<Ikev2VpnProfile> shim =
+                Ikev2VpnProfileShimImpl.newInstance(profile);
+        assertTrue(shim.isAutomaticIpVersionSelectionEnabled());
+    }
+
     private static class CertificateAndKey {
         public final X509Certificate cert;
         public final PrivateKey key;
diff --git a/tests/cts/net/src/android/net/cts/IpSecManagerTest.java b/tests/cts/net/src/android/net/cts/IpSecManagerTest.java
index 4fa0080..9c30811 100644
--- a/tests/cts/net/src/android/net/cts/IpSecManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/IpSecManagerTest.java
@@ -395,7 +395,7 @@
     private static class StatsChecker {
         private static final double ERROR_MARGIN_BYTES = 1.05;
         private static final double ERROR_MARGIN_PKTS = 1.05;
-        private static final int MAX_WAIT_TIME_MILLIS = 1000;
+        private static final int MAX_WAIT_TIME_MILLIS = 3000;
 
         private static long uidTxBytes;
         private static long uidRxBytes;
diff --git a/tests/cts/net/src/android/net/cts/NsdManagerTest.kt b/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
index 093c7f8..6fd2321 100644
--- a/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
+++ b/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
@@ -19,12 +19,17 @@
 import android.app.compat.CompatChanges
 import android.net.ConnectivityManager
 import android.net.ConnectivityManager.NetworkCallback
+import android.net.InetAddresses.parseNumericAddress
+import android.net.LinkAddress
 import android.net.LinkProperties
+import android.net.LocalSocket
+import android.net.LocalSocketAddress
 import android.net.Network
 import android.net.NetworkAgentConfig
 import android.net.NetworkCapabilities
 import android.net.NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED
 import android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED
+import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED
 import android.net.NetworkCapabilities.TRANSPORT_TEST
 import android.net.NetworkRequest
 import android.net.TestNetworkInterface
@@ -60,26 +65,43 @@
 import android.os.HandlerThread
 import android.os.Process.myTid
 import android.platform.test.annotations.AppModeFull
+import android.system.ErrnoException
+import android.system.Os
+import android.system.OsConstants.AF_INET6
+import android.system.OsConstants.EADDRNOTAVAIL
+import android.system.OsConstants.ENETUNREACH
+import android.system.OsConstants.IPPROTO_UDP
+import android.system.OsConstants.SOCK_DGRAM
 import android.util.Log
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.runner.AndroidJUnit4
+import com.android.compatibility.common.util.PollingCheck
+import com.android.compatibility.common.util.PropertyUtil
+import com.android.modules.utils.build.SdkLevel.isAtLeastU
 import com.android.net.module.util.ArrayTrackRecord
 import com.android.net.module.util.TrackRecord
 import com.android.networkstack.apishim.NsdShimImpl
 import com.android.networkstack.apishim.common.NsdShim
 import com.android.testutils.ConnectivityModuleTest
 import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.RecorderCallback.CallbackEntry.CapabilitiesChanged
+import com.android.testutils.RecorderCallback.CallbackEntry.LinkPropertiesChanged
 import com.android.testutils.TestableNetworkAgent
+import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnNetworkCreated
 import com.android.testutils.TestableNetworkCallback
 import com.android.testutils.filters.CtsNetTestCasesMaxTargetSdk30
+import com.android.testutils.filters.CtsNetTestCasesMaxTargetSdk33
 import com.android.testutils.runAsShell
 import com.android.testutils.tryTest
 import com.android.testutils.waitForIdle
 import java.io.File
+import java.io.IOException
+import java.net.NetworkInterface
 import java.net.ServerSocket
 import java.nio.charset.StandardCharsets
 import java.util.Random
 import java.util.concurrent.Executor
+import kotlin.math.min
 import kotlin.test.assertEquals
 import kotlin.test.assertFailsWith
 import kotlin.test.assertNotNull
@@ -346,24 +368,56 @@
                 .build(), cb)
         val agent = registerTestNetworkAgent(iface.interfaceName)
         val network = agent.network ?: fail("Registered agent should have a network")
+
+        cb.eventuallyExpect<LinkPropertiesChanged>(TIMEOUT_MS) {
+            it.lp.linkAddresses.isNotEmpty()
+        }
+
         // The network has no INTERNET capability, so will be marked validated immediately
-        cb.expectAvailableThenValidatedCallbacks(network, TIMEOUT_MS)
+        // It does not matter if validated capabilities come before/after the link addresses change
+        cb.eventuallyExpect<CapabilitiesChanged>(TIMEOUT_MS, from = 0) {
+            it.caps.hasCapability(NET_CAPABILITY_VALIDATED)
+        }
         return TestTapNetwork(iface, cb, agent, network)
     }
 
     private fun registerTestNetworkAgent(ifaceName: String): TestableNetworkAgent {
+        val lp = LinkProperties().apply {
+            interfaceName = ifaceName
+        }
+
         val agent = TestableNetworkAgent(context, handlerThread.looper,
                 NetworkCapabilities().apply {
                     removeCapability(NET_CAPABILITY_TRUSTED)
                     addTransportType(TRANSPORT_TEST)
                     setNetworkSpecifier(TestNetworkSpecifier(ifaceName))
-                },
-                LinkProperties().apply {
-                    interfaceName = ifaceName
-                },
-                NetworkAgentConfig.Builder().build())
-        agent.register()
+                }, lp, NetworkAgentConfig.Builder().build())
+        val network = agent.register()
         agent.markConnected()
+        agent.expectCallback<OnNetworkCreated>()
+
+        // Wait until the link-local address can be used. Address flags are not available without
+        // elevated permissions, so check that bindSocket works.
+        PollingCheck.check("No usable v6 address on interface after $TIMEOUT_MS ms", TIMEOUT_MS) {
+            val sock = Os.socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP)
+            tryTest {
+                network.bindSocket(sock)
+                Os.connect(sock, parseNumericAddress("ff02::fb%$ifaceName"), 12345)
+                true
+            }.catch<ErrnoException> {
+                if (it.errno != ENETUNREACH && it.errno != EADDRNOTAVAIL) {
+                    throw it
+                }
+                false
+            } cleanup {
+                Os.close(sock)
+            }
+        }
+
+        lp.setLinkAddresses(NetworkInterface.getByName(ifaceName).interfaceAddresses.map {
+            LinkAddress(it.address, it.networkPrefixLength.toInt())
+        })
+        agent.sendLinkProperties(lp)
         return agent
     }
 
@@ -371,8 +425,9 @@
     fun tearDown() {
         if (TestUtils.shouldTestTApis()) {
             runAsShell(MANAGE_TEST_NETWORKS) {
-                testNetwork1.close(cm)
-                testNetwork2.close(cm)
+                // Avoid throwing here if initializing failed in setUp
+                if (this::testNetwork1.isInitialized) testNetwork1.close(cm)
+                if (this::testNetwork2.isInitialized) testNetwork2.close(cm)
             }
         }
         handlerThread.waitForIdle(TIMEOUT_MS)
@@ -458,7 +513,12 @@
         assertTrue(resolvedService.attributes.containsKey("nullBinaryDataAttr"))
         assertNull(resolvedService.attributes["nullBinaryDataAttr"])
         assertTrue(resolvedService.attributes.containsKey("emptyBinaryDataAttr"))
-        assertNull(resolvedService.attributes["emptyBinaryDataAttr"])
+        // TODO: change the check to target SDK U when this is what the code implements
+        if (isAtLeastU()) {
+            assertArrayEquals(byteArrayOf(), resolvedService.attributes["emptyBinaryDataAttr"])
+        } else {
+            assertNull(resolvedService.attributes["emptyBinaryDataAttr"])
+        }
         assertEquals(localPort, resolvedService.port)
 
         // Unregister the service
@@ -763,6 +823,69 @@
         }
     }
 
+    private fun checkConnectSocketToMdnsd(shouldFail: Boolean) {
+        val discoveryRecord = NsdDiscoveryRecord()
+        val localSocket = LocalSocket()
+        tryTest {
+            // Discover any service from NsdManager to enforce NsdService to start the mdnsd.
+            nsdManager.discoverServices(serviceType, NsdManager.PROTOCOL_DNS_SD, discoveryRecord)
+            discoveryRecord.expectCallback<DiscoveryStarted>()
+
+            // Checks the /dev/socket/mdnsd is created.
+            val socket = File("/dev/socket/mdnsd")
+            val doesSocketExist = PollingCheck.waitFor(
+                TIMEOUT_MS,
+                {
+                    socket.exists()
+                },
+                { doesSocketExist ->
+                    doesSocketExist
+                },
+            )
+
+            // If the socket is not created, then no need to check the access.
+            if (doesSocketExist) {
+                // Create a LocalSocket and try to connect to mdnsd.
+                assertFalse("LocalSocket is connected.", localSocket.isConnected)
+                val address = LocalSocketAddress("mdnsd", LocalSocketAddress.Namespace.RESERVED)
+                if (shouldFail) {
+                    assertFailsWith<IOException>("Expect fail but socket connected") {
+                        localSocket.connect(address)
+                    }
+                } else {
+                    localSocket.connect(address)
+                    assertTrue("LocalSocket is not connected.", localSocket.isConnected)
+                }
+            }
+        } cleanup {
+            localSocket.close()
+            nsdManager.stopServiceDiscovery(discoveryRecord)
+            discoveryRecord.expectCallback<DiscoveryStopped>()
+        }
+    }
+
+    /**
+     * Starting from Android U, the access to the /dev/socket/mdnsd is blocked by the
+     * sepolicy(b/265364111).
+     */
+    @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+    @Test
+    fun testCannotConnectSocketToMdnsd() {
+        val targetSdkVersion = context.packageManager
+                .getTargetSdkVersion(context.applicationInfo.packageName)
+        assumeTrue(targetSdkVersion > Build.VERSION_CODES.TIRAMISU)
+        val firstApiLevel = min(PropertyUtil.getFirstApiLevel(), PropertyUtil.getVendorApiLevel())
+        // The sepolicy is implemented in the vendor image, so the access may not be blocked if
+        // the vendor image is not update to date.
+        assumeTrue(firstApiLevel > Build.VERSION_CODES.TIRAMISU)
+        checkConnectSocketToMdnsd(shouldFail = true)
+    }
+
+    @Test @CtsNetTestCasesMaxTargetSdk33("mdnsd socket is accessible up to target SDK 33")
+    fun testCanConnectSocketToMdnsd() {
+        checkConnectSocketToMdnsd(shouldFail = false)
+    }
+
     @Test @CtsNetTestCasesMaxTargetSdk30("Socket is started with the service up to target SDK 30")
     fun testManagerCreatesLegacySocket() {
         nsdManager // Ensure the lazy-init member is initialized, so NsdManager is created
@@ -809,12 +932,10 @@
 
     @Test
     fun testRegisterServiceInfoCallback() {
-        // This test requires shims supporting U+ APIs (NsdManager.subscribeService)
+        // This test requires shims supporting U+ APIs (NsdManager.registerServiceInfoCallback)
         assumeTrue(TestUtils.shouldTestUApis())
 
-        // Ensure Wi-Fi network connected and get addresses
-        val wifiNetwork = ctsNetUtils.ensureWifiConnected()
-        val lp = cm.getLinkProperties(wifiNetwork)
+        val lp = cm.getLinkProperties(testNetwork1.network)
         assertNotNull(lp)
         val addresses = lp.addresses
         assertFalse(addresses.isEmpty())
@@ -822,33 +943,31 @@
         val si = NsdServiceInfo().apply {
             serviceType = this@NsdManagerTest.serviceType
             serviceName = this@NsdManagerTest.serviceName
-            network = wifiNetwork
+            network = testNetwork1.network
             port = 12345 // Test won't try to connect so port does not matter
         }
 
-        // Register service on Wi-Fi network
+        // Register service on the network
         val registrationRecord = NsdRegistrationRecord()
         registerService(registrationRecord, si)
 
         val discoveryRecord = NsdDiscoveryRecord()
         val cbRecord = NsdServiceInfoCallbackRecord()
         tryTest {
-            // Discover service on Wi-Fi network.
+            // Discover service on the network.
             nsdShim.discoverServices(nsdManager, serviceType, NsdManager.PROTOCOL_DNS_SD,
-                    wifiNetwork, Executor { it.run() }, discoveryRecord)
+                    testNetwork1.network, Executor { it.run() }, discoveryRecord)
             val foundInfo = discoveryRecord.waitForServiceDiscovered(
-                    serviceName, wifiNetwork)
+                    serviceName, testNetwork1.network)
 
-            // Subscribe to service and check the addresses are the same as Wi-Fi addresses
+            // Register service callback and check the addresses are the same as network addresses
             nsdShim.registerServiceInfoCallback(nsdManager, foundInfo, { it.run() }, cbRecord)
-            for (i in addresses.indices) {
-                val subscribeCb = cbRecord.expectCallback<ServiceUpdated>()
-                assertEquals(foundInfo.serviceName, subscribeCb.serviceInfo.serviceName)
-                val hostAddresses = subscribeCb.serviceInfo.hostAddresses
-                assertEquals(i + 1, hostAddresses.size)
-                for (hostAddress in hostAddresses) {
-                    assertTrue(addresses.contains(hostAddress))
-                }
+            val serviceInfoCb = cbRecord.expectCallback<ServiceUpdated>()
+            assertEquals(foundInfo.serviceName, serviceInfoCb.serviceInfo.serviceName)
+            val hostAddresses = serviceInfoCb.serviceInfo.hostAddresses
+            assertEquals(addresses.size, hostAddresses.size)
+            for (hostAddress in hostAddresses) {
+                assertTrue(addresses.contains(hostAddress))
             }
         } cleanupStep {
             nsdManager.unregisterService(registrationRecord)
diff --git a/tests/unit/java/android/net/NetworkTemplateTest.kt b/tests/unit/java/android/net/NetworkTemplateTest.kt
index edbcea9..fc25fd8 100644
--- a/tests/unit/java/android/net/NetworkTemplateTest.kt
+++ b/tests/unit/java/android/net/NetworkTemplateTest.kt
@@ -130,10 +130,17 @@
                 mockContext, buildWifiNetworkState(null, TEST_WIFI_KEY1), true, 0)
         val identWifiImsi1Key1 = buildNetworkIdentity(
                 mockContext, buildWifiNetworkState(TEST_IMSI1, TEST_WIFI_KEY1), true, 0)
+        // This identity with a null wifiNetworkKey is to test matchesWifiNetworkKey won't crash
+        // the system when a null wifiNetworkKey is provided, which happens because of a bug in wifi
+        // and it should still match the wifi wildcard template. See b/266598304.
+        val identWifiNullKey = buildNetworkIdentity(
+                mockContext, buildWifiNetworkState(null /* subscriberId */,
+                null /* wifiNetworkKey */), true, 0)
 
         templateWifiWildcard.assertDoesNotMatch(identMobileImsi1)
         templateWifiWildcard.assertMatches(identWifiImsiNullKey1)
         templateWifiWildcard.assertMatches(identWifiImsi1Key1)
+        templateWifiWildcard.assertMatches(identWifiNullKey)
     }
 
     @Test
@@ -148,6 +155,9 @@
                 .setWifiNetworkKeys(setOf(TEST_WIFI_KEY1)).build()
         val templateWifiKeyAllImsi1 = NetworkTemplate.Builder(MATCH_WIFI)
                 .setSubscriberIds(setOf(TEST_IMSI1)).build()
+        val templateNullWifiKey = NetworkTemplate(MATCH_WIFI,
+                emptyArray<String>() /* subscriberIds */, arrayOf(null) /* wifiNetworkKeys */,
+                METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL, OEM_MANAGED_ALL)
 
         val identMobile1 = buildNetworkIdentity(mockContext, buildMobileNetworkState(TEST_IMSI1),
                 false, TelephonyManager.NETWORK_TYPE_UMTS)
@@ -159,6 +169,12 @@
                 mockContext, buildWifiNetworkState(TEST_IMSI2, TEST_WIFI_KEY1), true, 0)
         val identWifiImsi1Key2 = buildNetworkIdentity(
                 mockContext, buildWifiNetworkState(TEST_IMSI1, TEST_WIFI_KEY2), true, 0)
+        // This identity with a null wifiNetworkKey is to test the matchesWifiNetworkKey won't crash
+        // the system when a null wifiNetworkKey is provided, which would happen in some unknown
+        // cases, see b/266598304.
+        val identWifiNullKey = buildNetworkIdentity(
+                mockContext, buildWifiNetworkState(null /* subscriberId */,
+                null /* wifiNetworkKey */), true, 0)
 
         // Verify that template with WiFi Network Key only matches any subscriberId and
         // specific WiFi Network Key.
@@ -191,6 +207,12 @@
         templateWifiKeyAllImsi1.assertMatches(identWifiImsi1Key1)
         templateWifiKeyAllImsi1.assertDoesNotMatch(identWifiImsi2Key1)
         templateWifiKeyAllImsi1.assertMatches(identWifiImsi1Key2)
+
+        // Test a network identity with null wifiNetworkKey won't crash.
+        // It should not match a template with wifiNetworkKeys is non-null.
+        // Also, it should not match a template with wifiNetworkKeys that contains null.
+        templateWifiKey1.assertDoesNotMatch(identWifiNullKey)
+        templateNullWifiKey.assertDoesNotMatch(identWifiNullKey)
     }
 
     @Test
diff --git a/tests/unit/java/android/net/util/KeepaliveUtilsTest.kt b/tests/unit/java/android/net/util/KeepaliveUtilsTest.kt
index 9203f8f..cca0b66 100644
--- a/tests/unit/java/android/net/util/KeepaliveUtilsTest.kt
+++ b/tests/unit/java/android/net/util/KeepaliveUtilsTest.kt
@@ -104,7 +104,7 @@
 
         // Check valid customization generates expected array.
         val validRes = arrayOf("0,3", "1,0", "4,4")
-        val expectedValidRes = intArrayOf(3, 0, 0, 0, 4, 0, 0, 0, 0)
+        val expectedValidRes = intArrayOf(3, 0, 0, 0, 4, 0, 0, 0, 0, 0)
 
         val mockContext = getMockedContextWithStringArrayRes(
                 R.array.config_networkSupportedKeepaliveCount,
diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
index 5b98237..c1c6a8a 100755
--- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
@@ -183,6 +183,8 @@
 import static com.android.testutils.RecorderCallback.CallbackEntry.UNAVAILABLE;
 import static com.android.testutils.TestPermissionUtil.runAsShell;
 
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsString;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
@@ -228,6 +230,7 @@
 import android.app.PendingIntent;
 import android.app.admin.DevicePolicyManager;
 import android.app.usage.NetworkStatsManager;
+import android.compat.testing.PlatformCompatChangeRule;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.ContentProvider;
@@ -312,6 +315,7 @@
 import android.net.Uri;
 import android.net.VpnManager;
 import android.net.VpnTransportInfo;
+import android.net.connectivity.ConnectivityCompatChanges;
 import android.net.metrics.IpConnectivityLog;
 import android.net.netd.aidl.NativeUidRangeConfig;
 import android.net.networkstack.NetworkStackClientBase;
@@ -379,6 +383,7 @@
 import com.android.server.ConnectivityService.ConnectivityDiagnosticsCallbackInfo;
 import com.android.server.ConnectivityService.NetworkRequestInfo;
 import com.android.server.ConnectivityServiceTest.ConnectivityServiceDependencies.ReportedInterfaces;
+import com.android.server.connectivity.ApplicationSelfCertifiedNetworkCapabilities;
 import com.android.server.connectivity.AutomaticOnOffKeepaliveTracker;
 import com.android.server.connectivity.CarrierPrivilegeAuthenticator;
 import com.android.server.connectivity.ClatCoordinator;
@@ -406,6 +411,9 @@
 import com.android.testutils.TestableNetworkCallback;
 import com.android.testutils.TestableNetworkOfferCallback;
 
+import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
+import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
+
 import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
@@ -475,6 +483,9 @@
     @Rule
     public final DevSdkIgnoreRule ignoreRule = new DevSdkIgnoreRule();
 
+    @Rule
+    public final PlatformCompatChangeRule compatChangeRule = new PlatformCompatChangeRule();
+
     private static final int TIMEOUT_MS = 2_000;
     // Broadcasts can take a long time to be delivered. The test will not wait for that long unless
     // there is a failure, so use a long timeout.
@@ -853,7 +864,8 @@
                     verify(mBroadcastOptionsShim).setDeliveryGroupMatchingKey(
                             eq(CONNECTIVITY_ACTION),
                             eq(createDeliveryGroupKeyForConnectivityAction(ni)));
-                    verify(mBroadcastOptionsShim).setDeferUntilActive(eq(true));
+                    verify(mBroadcastOptionsShim).setDeferralPolicy(
+                            eq(ConstantsShim.DEFERRAL_POLICY_UNTIL_ACTIVE));
                 } catch (UnsupportedApiLevelException e) {
                     throw new RuntimeException(e);
                 }
@@ -2105,6 +2117,37 @@
             reset(mBroadcastOptionsShim);
             return mBroadcastOptionsShim;
         }
+
+        @GuardedBy("this")
+        private boolean mForceDisableCompatChangeCheck = true;
+
+        /**
+         * By default, the {@link #isChangeEnabled(long, String, UserHandle)} will always return
+         * true as the mForceDisableCompatChangeCheck is true and compat change check logic is
+         * never executed. The compat change check logic can be turned on by calling this method.
+         * If this method is called, the
+         * {@link libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges} or
+         * {@link libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges} must be
+         * used to turn on/off the compat change flag.
+         */
+        private void enableCompatChangeCheck() {
+            synchronized (this) {
+                mForceDisableCompatChangeCheck = false;
+            }
+        }
+
+        @Override
+        public boolean isChangeEnabled(long changeId,
+                @NonNull final String packageName,
+                @NonNull final UserHandle user) {
+            synchronized (this) {
+                if (mForceDisableCompatChangeCheck) {
+                    return false;
+                } else {
+                    return super.isChangeEnabled(changeId, packageName, user);
+                }
+            }
+        }
     }
 
     private class AutomaticOnOffKeepaliveTrackerDependencies
@@ -6310,6 +6353,142 @@
         }
     }
 
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+    @DisableCompatChanges(ConnectivityCompatChanges.ENABLE_SELF_CERTIFIED_CAPABILITIES_DECLARATION)
+    public void testSelfCertifiedCapabilitiesDisabled()
+            throws Exception {
+        mDeps.enableCompatChangeCheck();
+        final NetworkRequest networkRequest = new NetworkRequest.Builder()
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_LATENCY)
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_BANDWIDTH)
+                .build();
+        final TestNetworkCallback cb = new TestNetworkCallback();
+        mCm.requestNetwork(networkRequest, cb);
+        mCm.unregisterNetworkCallback(cb);
+    }
+
+    /** Set the networkSliceResourceId to 0 will result in NameNotFoundException be thrown. */
+    private void setupMockForNetworkCapabilitiesResources(int networkSliceResourceId)
+            throws PackageManager.NameNotFoundException {
+        if (networkSliceResourceId == 0) {
+            doThrow(new PackageManager.NameNotFoundException()).when(mPackageManager).getProperty(
+                    ConstantsShim.PROPERTY_SELF_CERTIFIED_NETWORK_CAPABILITIES,
+                    mContext.getPackageName());
+        } else {
+            final PackageManager.Property property = new PackageManager.Property(
+                    ConstantsShim.PROPERTY_SELF_CERTIFIED_NETWORK_CAPABILITIES,
+                    networkSliceResourceId,
+                    true /* isResource */,
+                    mContext.getPackageName(),
+                    "dummyClass"
+            );
+            doReturn(property).when(mPackageManager).getProperty(
+                    ConstantsShim.PROPERTY_SELF_CERTIFIED_NETWORK_CAPABILITIES,
+                    mContext.getPackageName());
+            doReturn(mContext.getResources()).when(mPackageManager).getResourcesForApplication(
+                    mContext.getPackageName());
+        }
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+    @EnableCompatChanges(ConnectivityCompatChanges.ENABLE_SELF_CERTIFIED_CAPABILITIES_DECLARATION)
+    public void requestNetwork_withoutPrioritizeBandwidthDeclaration_shouldThrowException()
+            throws Exception {
+        mDeps.enableCompatChangeCheck();
+        setupMockForNetworkCapabilitiesResources(
+                com.android.frameworks.tests.net.R.xml.self_certified_capabilities_latency);
+        final NetworkRequest networkRequest = new NetworkRequest.Builder()
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_BANDWIDTH)
+                .build();
+        final TestNetworkCallback cb = new TestNetworkCallback();
+        final Exception e = assertThrows(SecurityException.class,
+                () -> mCm.requestNetwork(networkRequest, cb));
+        assertThat(e.getMessage(),
+                containsString(ApplicationSelfCertifiedNetworkCapabilities.PRIORITIZE_BANDWIDTH));
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+    @EnableCompatChanges(ConnectivityCompatChanges.ENABLE_SELF_CERTIFIED_CAPABILITIES_DECLARATION)
+    public void requestNetwork_withoutPrioritizeLatencyDeclaration_shouldThrowException()
+            throws Exception {
+        mDeps.enableCompatChangeCheck();
+        setupMockForNetworkCapabilitiesResources(
+                com.android.frameworks.tests.net.R.xml.self_certified_capabilities_bandwidth);
+        final NetworkRequest networkRequest = new NetworkRequest.Builder()
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_LATENCY)
+                .build();
+        final TestNetworkCallback cb = new TestNetworkCallback();
+        final Exception e = assertThrows(SecurityException.class,
+                () -> mCm.requestNetwork(networkRequest, cb));
+        assertThat(e.getMessage(),
+                containsString(ApplicationSelfCertifiedNetworkCapabilities.PRIORITIZE_LATENCY));
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+    @EnableCompatChanges(ConnectivityCompatChanges.ENABLE_SELF_CERTIFIED_CAPABILITIES_DECLARATION)
+    public void requestNetwork_withoutNetworkSliceProperty_shouldThrowException() throws Exception {
+        mDeps.enableCompatChangeCheck();
+        setupMockForNetworkCapabilitiesResources(0 /* networkSliceResourceId */);
+        final NetworkRequest networkRequest = new NetworkRequest.Builder()
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_LATENCY)
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_BANDWIDTH)
+                .build();
+        final TestNetworkCallback cb = new TestNetworkCallback();
+        final Exception e = assertThrows(SecurityException.class,
+                () -> mCm.requestNetwork(networkRequest, cb));
+        assertThat(e.getMessage(),
+                containsString(ConstantsShim.PROPERTY_SELF_CERTIFIED_NETWORK_CAPABILITIES));
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+    @EnableCompatChanges(ConnectivityCompatChanges.ENABLE_SELF_CERTIFIED_CAPABILITIES_DECLARATION)
+    public void requestNetwork_withNetworkSliceDeclaration_shouldSucceed() throws Exception {
+        mDeps.enableCompatChangeCheck();
+        setupMockForNetworkCapabilitiesResources(
+                com.android.frameworks.tests.net.R.xml.self_certified_capabilities_both);
+
+        final NetworkRequest networkRequest = new NetworkRequest.Builder()
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_LATENCY)
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_BANDWIDTH)
+                .build();
+        final TestNetworkCallback cb = new TestNetworkCallback();
+        mCm.requestNetwork(networkRequest, cb);
+        mCm.unregisterNetworkCallback(cb);
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+    @EnableCompatChanges(ConnectivityCompatChanges.ENABLE_SELF_CERTIFIED_CAPABILITIES_DECLARATION)
+    public void requestNetwork_withNetworkSliceDeclaration_shouldUseCache() throws Exception {
+        mDeps.enableCompatChangeCheck();
+        setupMockForNetworkCapabilitiesResources(
+                com.android.frameworks.tests.net.R.xml.self_certified_capabilities_both);
+
+        final NetworkRequest networkRequest = new NetworkRequest.Builder()
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_LATENCY)
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_BANDWIDTH)
+                .build();
+        final TestNetworkCallback cb = new TestNetworkCallback();
+        mCm.requestNetwork(networkRequest, cb);
+        mCm.unregisterNetworkCallback(cb);
+
+        // Second call should use caches
+        mCm.requestNetwork(networkRequest, cb);
+        mCm.unregisterNetworkCallback(cb);
+
+        // PackageManager's API only called once because the second call is using cache.
+        verify(mPackageManager, times(1)).getProperty(
+                ConstantsShim.PROPERTY_SELF_CERTIFIED_NETWORK_CAPABILITIES,
+                mContext.getPackageName());
+        verify(mPackageManager, times(1)).getResourcesForApplication(
+                mContext.getPackageName());
+    }
+
     /**
      * Validate the service throws if request with CBS but without carrier privilege.
      */
diff --git a/tests/unit/java/com/android/server/NsdServiceTest.java b/tests/unit/java/com/android/server/NsdServiceTest.java
index 0680772..0b48e08 100644
--- a/tests/unit/java/com/android/server/NsdServiceTest.java
+++ b/tests/unit/java/com/android/server/NsdServiceTest.java
@@ -21,6 +21,7 @@
 import static android.net.nsd.NsdManager.FAILURE_INTERNAL_ERROR;
 import static android.net.nsd.NsdManager.FAILURE_OPERATION_NOT_RUNNING;
 
+import static com.android.server.NsdService.constructServiceType;
 import static com.android.testutils.ContextUtils.mockService;
 
 import static libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
@@ -100,6 +101,7 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.net.InetAddress;
 import java.net.UnknownHostException;
 import java.util.LinkedList;
 import java.util.List;
@@ -703,119 +705,102 @@
     }
 
     private void verifyUpdatedServiceInfo(NsdServiceInfo info, String serviceName,
-            String serviceType, String address, int port, int interfaceIndex, Network network) {
+            String serviceType, List<InetAddress> address, int port, int interfaceIndex,
+            Network network) {
         assertEquals(serviceName, info.getServiceName());
         assertEquals(serviceType, info.getServiceType());
-        assertTrue(info.getHostAddresses().contains(parseNumericAddress(address)));
+        assertEquals(address, info.getHostAddresses());
         assertEquals(port, info.getPort());
         assertEquals(network, info.getNetwork());
         assertEquals(interfaceIndex, info.getInterfaceIndex());
     }
 
     @Test
-    public void testRegisterAndUnregisterServiceInfoCallback() throws RemoteException {
+    public void testRegisterAndUnregisterServiceInfoCallback() {
         final NsdManager client = connectClient(mService);
         final NsdServiceInfo request = new NsdServiceInfo(SERVICE_NAME, SERVICE_TYPE);
         final NsdManager.ServiceInfoCallback serviceInfoCallback = mock(
                 NsdManager.ServiceInfoCallback.class);
+        final String serviceTypeWithLocalDomain = SERVICE_TYPE + ".local";
+        final Network network = new Network(999);
+        request.setNetwork(network);
         client.registerServiceInfoCallback(request, Runnable::run, serviceInfoCallback);
         waitForIdle();
+        // Verify the registration callback start.
+        final ArgumentCaptor<MdnsServiceBrowserListener> listenerCaptor =
+                ArgumentCaptor.forClass(MdnsServiceBrowserListener.class);
+        verify(mSocketProvider).startMonitoringSockets();
+        verify(mDiscoveryManager).registerListener(eq(serviceTypeWithLocalDomain),
+                listenerCaptor.capture(), argThat(options -> network.equals(options.getNetwork())));
 
-        final IMDnsEventListener eventListener = getEventListener();
-        final ArgumentCaptor<Integer> resolvIdCaptor = ArgumentCaptor.forClass(Integer.class);
-        verify(mMockMDnsM).resolve(resolvIdCaptor.capture(), eq(SERVICE_NAME), eq(SERVICE_TYPE),
-                eq("local.") /* domain */, eq(IFACE_IDX_ANY));
-
-        // Resolve service successfully.
-        final ResolutionInfo resolutionInfo = new ResolutionInfo(
-                resolvIdCaptor.getValue(),
-                IMDnsEventListener.SERVICE_RESOLVED,
-                null /* serviceName */,
-                null /* serviceType */,
-                null /* domain */,
-                SERVICE_FULL_NAME,
-                DOMAIN_NAME,
+        final MdnsServiceBrowserListener listener = listenerCaptor.getValue();
+        final MdnsServiceInfo mdnsServiceInfo = new MdnsServiceInfo(
+                SERVICE_NAME,
+                serviceTypeWithLocalDomain.split("\\."),
+                List.of(), /* subtypes */
+                new String[]{"android", "local"}, /* hostName */
                 PORT,
-                new byte[0] /* txtRecord */,
-                IFACE_IDX_ANY);
-        doReturn(true).when(mMockMDnsM).getServiceAddress(anyInt(), any(), anyInt());
-        eventListener.onServiceResolutionStatus(resolutionInfo);
-        waitForIdle();
+                List.of(IPV4_ADDRESS),
+                List.of(IPV6_ADDRESS),
+                List.of() /* textStrings */,
+                List.of() /* textEntries */,
+                1234,
+                network);
 
-        final ArgumentCaptor<Integer> getAddrIdCaptor = ArgumentCaptor.forClass(Integer.class);
-        verify(mMockMDnsM).getServiceAddress(getAddrIdCaptor.capture(), eq(DOMAIN_NAME),
-                eq(IFACE_IDX_ANY));
-
-        // First address info
-        final String v4Address = "192.0.2.1";
-        final String v6Address = "2001:db8::";
-        final GetAddressInfo addressInfo1 = new GetAddressInfo(
-                getAddrIdCaptor.getValue(),
-                IMDnsEventListener.SERVICE_GET_ADDR_SUCCESS,
-                SERVICE_FULL_NAME,
-                v4Address,
-                IFACE_IDX_ANY,
-                999 /* netId */);
-        eventListener.onGettingServiceAddressStatus(addressInfo1);
-        waitForIdle();
-
+        // Verify onServiceFound callback
+        listener.onServiceFound(mdnsServiceInfo);
         final ArgumentCaptor<NsdServiceInfo> updateInfoCaptor =
                 ArgumentCaptor.forClass(NsdServiceInfo.class);
         verify(serviceInfoCallback, timeout(TIMEOUT_MS).times(1))
                 .onServiceUpdated(updateInfoCaptor.capture());
         verifyUpdatedServiceInfo(updateInfoCaptor.getAllValues().get(0) /* info */, SERVICE_NAME,
-                "." + SERVICE_TYPE, v4Address, PORT, IFACE_IDX_ANY, new Network(999));
+                SERVICE_TYPE,
+                List.of(parseNumericAddress(IPV4_ADDRESS), parseNumericAddress(IPV6_ADDRESS)),
+                PORT, IFACE_IDX_ANY, new Network(999));
 
-        // Second address info
-        final GetAddressInfo addressInfo2 = new GetAddressInfo(
-                getAddrIdCaptor.getValue(),
-                IMDnsEventListener.SERVICE_GET_ADDR_SUCCESS,
-                SERVICE_FULL_NAME,
-                v6Address,
-                IFACE_IDX_ANY,
-                999 /* netId */);
-        eventListener.onGettingServiceAddressStatus(addressInfo2);
-        waitForIdle();
+        // Service addresses changed.
+        final String v4Address = "192.0.2.1";
+        final String v6Address = "2001:db8::1";
+        final MdnsServiceInfo updatedServiceInfo = new MdnsServiceInfo(
+                SERVICE_NAME,
+                serviceTypeWithLocalDomain.split("\\."),
+                List.of(), /* subtypes */
+                new String[]{"android", "local"}, /* hostName */
+                PORT,
+                List.of(v4Address),
+                List.of(v6Address),
+                List.of() /* textStrings */,
+                List.of() /* textEntries */,
+                1234,
+                network);
 
+        // Verify onServiceUpdated callback.
+        listener.onServiceUpdated(updatedServiceInfo);
         verify(serviceInfoCallback, timeout(TIMEOUT_MS).times(2))
                 .onServiceUpdated(updateInfoCaptor.capture());
-        verifyUpdatedServiceInfo(updateInfoCaptor.getAllValues().get(1) /* info */, SERVICE_NAME,
-                "." + SERVICE_TYPE, v6Address, PORT, IFACE_IDX_ANY, new Network(999));
+        verifyUpdatedServiceInfo(updateInfoCaptor.getAllValues().get(2) /* info */, SERVICE_NAME,
+                SERVICE_TYPE,
+                List.of(parseNumericAddress(v4Address), parseNumericAddress(v6Address)),
+                PORT, IFACE_IDX_ANY, new Network(999));
 
+        // Verify service callback unregistration.
         client.unregisterServiceInfoCallback(serviceInfoCallback);
         waitForIdle();
-
         verify(serviceInfoCallback, timeout(TIMEOUT_MS)).onServiceInfoCallbackUnregistered();
     }
 
     @Test
-    public void testRegisterServiceCallbackFailed() throws Exception {
+    public void testRegisterServiceCallbackFailed() {
         final NsdManager client = connectClient(mService);
-        final NsdServiceInfo request = new NsdServiceInfo(SERVICE_NAME, SERVICE_TYPE);
-        final NsdManager.ServiceInfoCallback subscribeListener = mock(
+        final String invalidServiceType = "a_service";
+        final NsdServiceInfo request = new NsdServiceInfo(SERVICE_NAME, invalidServiceType);
+        final NsdManager.ServiceInfoCallback serviceInfoCallback = mock(
                 NsdManager.ServiceInfoCallback.class);
-        client.registerServiceInfoCallback(request, Runnable::run, subscribeListener);
+        client.registerServiceInfoCallback(request, Runnable::run, serviceInfoCallback);
         waitForIdle();
 
-        final IMDnsEventListener eventListener = getEventListener();
-        final ArgumentCaptor<Integer> resolvIdCaptor = ArgumentCaptor.forClass(Integer.class);
-        verify(mMockMDnsM).resolve(resolvIdCaptor.capture(), eq(SERVICE_NAME), eq(SERVICE_TYPE),
-                eq("local.") /* domain */, eq(IFACE_IDX_ANY));
-
-        // Fail to resolve service.
-        final ResolutionInfo resolutionFailedInfo = new ResolutionInfo(
-                resolvIdCaptor.getValue(),
-                IMDnsEventListener.SERVICE_RESOLUTION_FAILED,
-                null /* serviceName */,
-                null /* serviceType */,
-                null /* domain */,
-                null /* serviceFullName */,
-                null /* domainName */,
-                0 /* port */,
-                new byte[0] /* txtRecord */,
-                IFACE_IDX_ANY);
-        eventListener.onServiceResolutionStatus(resolutionFailedInfo);
-        verify(subscribeListener, timeout(TIMEOUT_MS))
+        // Fail to register service callback.
+        verify(serviceInfoCallback, timeout(TIMEOUT_MS))
                 .onServiceInfoCallbackRegistrationFailed(eq(FAILURE_BAD_PARAMETERS));
     }
 
@@ -895,8 +880,8 @@
                 List.of(), /* subtypes */
                 new String[] {"android", "local"}, /* hostName */
                 12345, /* port */
-                IPV4_ADDRESS,
-                IPV6_ADDRESS,
+                List.of(IPV4_ADDRESS),
+                List.of(IPV6_ADDRESS),
                 List.of(), /* textStrings */
                 List.of(), /* textEntries */
                 1234, /* interfaceIndex */
@@ -915,8 +900,8 @@
                 null, /* subtypes */
                 null, /* hostName */
                 0, /* port */
-                null, /* ipv4Address */
-                null, /* ipv6Address */
+                List.of(), /* ipv4Address */
+                List.of(), /* ipv6Address */
                 null, /* textStrings */
                 null, /* textEntries */
                 1234, /* interfaceIndex */
@@ -932,7 +917,7 @@
         waitForIdle();
         verify(mDiscoveryManager).unregisterListener(eq(serviceTypeWithLocalDomain), any());
         verify(discListener, timeout(TIMEOUT_MS)).onDiscoveryStopped(SERVICE_TYPE);
-        verify(mSocketProvider, timeout(CLEANUP_DELAY_MS + TIMEOUT_MS)).stopMonitoringSockets();
+        verify(mSocketProvider, timeout(CLEANUP_DELAY_MS + TIMEOUT_MS)).requestStopWhenInactive();
     }
 
     @Test
@@ -981,7 +966,9 @@
         waitForIdle();
         verify(mSocketProvider).startMonitoringSockets();
         verify(mDiscoveryManager).registerListener(eq(constructedServiceType),
-                listenerCaptor.capture(), argThat(options -> network.equals(options.getNetwork())));
+                listenerCaptor.capture(), argThat(options ->
+                        network.equals(options.getNetwork())
+                                && SERVICE_NAME.equals(options.getResolveInstanceName())));
 
         final MdnsServiceBrowserListener listener = listenerCaptor.getValue();
         final MdnsServiceInfo mdnsServiceInfo = new MdnsServiceInfo(
@@ -990,8 +977,8 @@
                 List.of(), /* subtypes */
                 new String[]{"android", "local"}, /* hostName */
                 PORT,
-                IPV4_ADDRESS,
-                IPV6_ADDRESS,
+                List.of(IPV4_ADDRESS),
+                List.of("2001:db8::1", "2001:db8::2"),
                 List.of() /* textStrings */,
                 List.of(MdnsServiceInfo.TextEntry.fromBytes(new byte[]{
                         'k', 'e', 'y', '=', (byte) 0xFF, (byte) 0xFE})) /* textEntries */,
@@ -1011,12 +998,17 @@
         assertEquals(1, info.getAttributes().size());
         assertArrayEquals(new byte[]{(byte) 0xFF, (byte) 0xFE}, info.getAttributes().get("key"));
         assertEquals(parseNumericAddress(IPV4_ADDRESS), info.getHost());
+        assertEquals(3, info.getHostAddresses().size());
+        assertTrue(info.getHostAddresses().stream().anyMatch(
+                address -> address.equals(parseNumericAddress("2001:db8::1"))));
+        assertTrue(info.getHostAddresses().stream().anyMatch(
+                address -> address.equals(parseNumericAddress("2001:db8::2"))));
         assertEquals(network, info.getNetwork());
 
         // Verify the listener has been unregistered.
         verify(mDiscoveryManager, timeout(TIMEOUT_MS))
                 .unregisterListener(eq(constructedServiceType), any());
-        verify(mSocketProvider, timeout(CLEANUP_DELAY_MS + TIMEOUT_MS)).stopMonitoringSockets();
+        verify(mSocketProvider, timeout(CLEANUP_DELAY_MS + TIMEOUT_MS)).requestStopWhenInactive();
     }
 
     @Test
@@ -1055,6 +1047,54 @@
     }
 
     @Test
+    public void testTypeSpecificFeatureFlagging() {
+        doReturn("_type1._tcp:flag1,_type2._tcp:flag2").when(mDeps).getTypeAllowlistFlags();
+        doReturn(true).when(mDeps).isFeatureEnabled(any(),
+                eq("mdns_discovery_manager_allowlist_flag1_version"));
+        doReturn(true).when(mDeps).isFeatureEnabled(any(),
+                eq("mdns_advertiser_allowlist_flag2_version"));
+
+        final NsdManager client = connectClient(mService);
+        final NsdServiceInfo service1 = new NsdServiceInfo(SERVICE_NAME, "_type1._tcp");
+        service1.setHostAddresses(List.of(parseNumericAddress("2001:db8::123")));
+        service1.setPort(1234);
+        final NsdServiceInfo service2 = new NsdServiceInfo(SERVICE_NAME, "_type2._tcp");
+        service2.setHostAddresses(List.of(parseNumericAddress("2001:db8::123")));
+        service2.setPort(1234);
+
+        client.discoverServices(service1.getServiceType(),
+                NsdManager.PROTOCOL_DNS_SD, mock(DiscoveryListener.class));
+        client.discoverServices(service2.getServiceType(),
+                NsdManager.PROTOCOL_DNS_SD, mock(DiscoveryListener.class));
+        waitForIdle();
+
+        // The DiscoveryManager is enabled for _type1 but not _type2
+        verify(mDiscoveryManager).registerListener(eq("_type1._tcp.local"), any(), any());
+        verify(mDiscoveryManager, never()).registerListener(
+                eq("_type2._tcp.local"), any(), any());
+
+        client.resolveService(service1, mock(ResolveListener.class));
+        client.resolveService(service2, mock(ResolveListener.class));
+        waitForIdle();
+
+        // Same behavior for resolve
+        verify(mDiscoveryManager, times(2)).registerListener(
+                eq("_type1._tcp.local"), any(), any());
+        verify(mDiscoveryManager, never()).registerListener(
+                eq("_type2._tcp.local"), any(), any());
+
+        client.registerService(service1, NsdManager.PROTOCOL_DNS_SD,
+                mock(RegistrationListener.class));
+        client.registerService(service2, NsdManager.PROTOCOL_DNS_SD,
+                mock(RegistrationListener.class));
+        waitForIdle();
+
+        // The advertiser is enabled for _type2 but not _type1
+        verify(mAdvertiser, never()).addService(anyInt(), argThat(info -> matches(info, service1)));
+        verify(mAdvertiser).addService(anyInt(), argThat(info -> matches(info, service2)));
+    }
+
+    @Test
     public void testAdvertiseWithMdnsAdvertiser() {
         setMdnsAdvertiserEnabled();
 
@@ -1090,7 +1130,7 @@
         verify(mAdvertiser).removeService(idCaptor.getValue());
         verify(regListener, timeout(TIMEOUT_MS)).onServiceUnregistered(
                 argThat(info -> matches(info, regInfo)));
-        verify(mSocketProvider, timeout(TIMEOUT_MS)).stopMonitoringSockets();
+        verify(mSocketProvider, timeout(TIMEOUT_MS)).requestStopWhenInactive();
     }
 
     @Test
@@ -1150,6 +1190,50 @@
                 argThat(info -> matches(info, new NsdServiceInfo(regInfo.getServiceName(), null))));
     }
 
+    @Test
+    public void testStopServiceResolutionWithMdnsDiscoveryManager() {
+        setMdnsDiscoveryManagerEnabled();
+
+        final NsdManager client = connectClient(mService);
+        final ResolveListener resolveListener = mock(ResolveListener.class);
+        final Network network = new Network(999);
+        final String serviceType = "_nsd._service._tcp";
+        final String constructedServiceType = "_nsd._sub._service._tcp.local";
+        final ArgumentCaptor<MdnsServiceBrowserListener> listenerCaptor =
+                ArgumentCaptor.forClass(MdnsServiceBrowserListener.class);
+        final NsdServiceInfo request = new NsdServiceInfo(SERVICE_NAME, serviceType);
+        request.setNetwork(network);
+        client.resolveService(request, resolveListener);
+        waitForIdle();
+        verify(mSocketProvider).startMonitoringSockets();
+        verify(mDiscoveryManager).registerListener(eq(constructedServiceType),
+                listenerCaptor.capture(), argThat(options -> network.equals(options.getNetwork())));
+
+        client.stopServiceResolution(resolveListener);
+        waitForIdle();
+
+        // Verify the listener has been unregistered.
+        verify(mDiscoveryManager, timeout(TIMEOUT_MS))
+                .unregisterListener(eq(constructedServiceType), eq(listenerCaptor.getValue()));
+        verify(resolveListener, timeout(TIMEOUT_MS)).onResolutionStopped(argThat(ns ->
+                request.getServiceName().equals(ns.getServiceName())
+                        && request.getServiceType().equals(ns.getServiceType())));
+        verify(mSocketProvider, timeout(CLEANUP_DELAY_MS + TIMEOUT_MS)).requestStopWhenInactive();
+    }
+
+    @Test
+    public void testConstructServiceType() {
+        final String serviceType1 = "test._tcp";
+        final String serviceType2 = "_test._quic";
+        final String serviceType3 = "_123._udp.";
+        final String serviceType4 = "_TEST._999._tcp.";
+
+        assertEquals(null, constructServiceType(serviceType1));
+        assertEquals(null, constructServiceType(serviceType2));
+        assertEquals("_123._udp", constructServiceType(serviceType3));
+        assertEquals("_TEST._sub._999._tcp", constructServiceType(serviceType4));
+    }
+
     private void waitForIdle() {
         HandlerUtils.waitForIdle(mHandler, TIMEOUT_MS);
     }
diff --git a/tests/unit/java/com/android/server/connectivity/ApplicationSelfCertifiedNetworkCapabilitiesTest.kt b/tests/unit/java/com/android/server/connectivity/ApplicationSelfCertifiedNetworkCapabilitiesTest.kt
new file mode 100644
index 0000000..f2d7aaa
--- /dev/null
+++ b/tests/unit/java/com/android/server/connectivity/ApplicationSelfCertifiedNetworkCapabilitiesTest.kt
@@ -0,0 +1,96 @@
+/*
+ * 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 com.android.server.connectivity
+
+import android.net.NetworkCapabilities
+import android.os.Build
+import androidx.test.InstrumentationRegistry
+import androidx.test.filters.SmallTest
+import com.android.frameworks.tests.net.R
+import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.DevSdkIgnoreRunner
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.assertFailsWith
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(DevSdkIgnoreRunner::class)
+@SmallTest
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+class ApplicationSelfCertifiedNetworkCapabilitiesTest {
+    private val mResource = InstrumentationRegistry.getContext().getResources()
+    private val bandwidthCapability = NetworkCapabilities.Builder().apply {
+        addCapability(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_BANDWIDTH)
+    }.build()
+    private val latencyCapability = NetworkCapabilities.Builder().apply {
+        addCapability(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_LATENCY)
+    }.build()
+    private val emptyCapability = NetworkCapabilities.Builder().build()
+    private val bothCapabilities = NetworkCapabilities.Builder().apply {
+        addCapability(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_BANDWIDTH)
+        addCapability(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_LATENCY)
+    }.build()
+
+    @Test
+    fun parseXmlWithWrongTag_shouldIgnoreWrongTag() {
+        val parser = mResource.getXml(
+            R.xml.self_certified_capabilities_wrong_tag
+        )
+        val selfDeclaredCaps = ApplicationSelfCertifiedNetworkCapabilities.createFromXml(parser)
+        selfDeclaredCaps.enforceSelfCertifiedNetworkCapabilitiesDeclared(latencyCapability)
+        selfDeclaredCaps.enforceSelfCertifiedNetworkCapabilitiesDeclared(bandwidthCapability)
+    }
+
+    @Test
+    fun parseXmlWithWrongDeclaration_shouldThrowException() {
+        val parser = mResource.getXml(
+            R.xml.self_certified_capabilities_wrong_declaration
+        )
+        val exception = assertFailsWith<InvalidTagException> {
+            ApplicationSelfCertifiedNetworkCapabilities.createFromXml(parser)
+        }
+        assertThat(exception.message).contains("network-capabilities-declaration1")
+    }
+
+    @Test
+    fun checkIfSelfCertifiedNetworkCapabilitiesDeclared_shouldThrowExceptionWhenNoDeclaration() {
+        val parser = mResource.getXml(R.xml.self_certified_capabilities_other)
+        val selfDeclaredCaps = ApplicationSelfCertifiedNetworkCapabilities.createFromXml(parser)
+        val exception1 = assertFailsWith<SecurityException> {
+            selfDeclaredCaps.enforceSelfCertifiedNetworkCapabilitiesDeclared(latencyCapability)
+        }
+        assertThat(exception1.message).contains(
+            ApplicationSelfCertifiedNetworkCapabilities.PRIORITIZE_LATENCY
+        )
+        val exception2 = assertFailsWith<SecurityException> {
+            selfDeclaredCaps.enforceSelfCertifiedNetworkCapabilitiesDeclared(bandwidthCapability)
+        }
+        assertThat(exception2.message).contains(
+            ApplicationSelfCertifiedNetworkCapabilities.PRIORITIZE_BANDWIDTH
+        )
+    }
+
+    @Test
+    fun checkIfSelfCertifiedNetworkCapabilitiesDeclared_shouldPassIfDeclarationExist() {
+        val parser = mResource.getXml(R.xml.self_certified_capabilities_both)
+        val selfDeclaredCaps = ApplicationSelfCertifiedNetworkCapabilities.createFromXml(parser)
+        selfDeclaredCaps.enforceSelfCertifiedNetworkCapabilitiesDeclared(latencyCapability)
+        selfDeclaredCaps.enforceSelfCertifiedNetworkCapabilitiesDeclared(bandwidthCapability)
+        selfDeclaredCaps.enforceSelfCertifiedNetworkCapabilitiesDeclared(bothCapabilities)
+        selfDeclaredCaps.enforceSelfCertifiedNetworkCapabilitiesDeclared(emptyCapability)
+    }
+}
diff --git a/tests/unit/java/com/android/server/connectivity/IpConnectivityMetricsTest.java b/tests/unit/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
index 719314a..5881a8e 100644
--- a/tests/unit/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
+++ b/tests/unit/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
@@ -80,6 +80,7 @@
 
     private static final byte[] MAC_ADDR =
             {(byte)0x84, (byte)0xc9, (byte)0xb2, (byte)0x6a, (byte)0xed, (byte)0x4b};
+    private static final long NET_HANDLE = new Network(4291).getNetworkHandle();
 
     @Mock Context mCtx;
     @Mock IIpConnectivityMetrics mMockService;
@@ -607,7 +608,7 @@
 
     void wakeupEvent(String iface, int uid, int ether, int ip, byte[] mac, String srcIp,
             String dstIp, int sport, int dport, long now) throws Exception {
-        String prefix = NetdEventListenerService.WAKEUP_EVENT_IFACE_PREFIX + iface;
+        String prefix = NET_HANDLE + ":" + iface;
         mNetdListener.onWakeupEvent(prefix, uid, ether, ip, mac, srcIp, dstIp, sport, dport, now);
     }
 
diff --git a/tests/unit/java/com/android/server/connectivity/NetdEventListenerServiceTest.java b/tests/unit/java/com/android/server/connectivity/NetdEventListenerServiceTest.java
index 7d6c3ae..f4b6464 100644
--- a/tests/unit/java/com/android/server/connectivity/NetdEventListenerServiceTest.java
+++ b/tests/unit/java/com/android/server/connectivity/NetdEventListenerServiceTest.java
@@ -60,6 +60,8 @@
     private static final String EXAMPLE_IPV4 = "192.0.2.1";
     private static final String EXAMPLE_IPV6 = "2001:db8:1200::2:1";
 
+    private static final long NET_HANDLE = new Network(5391).getNetworkHandle();
+
     private static final byte[] MAC_ADDR =
             {(byte)0x84, (byte)0xc9, (byte)0xb2, (byte)0x6a, (byte)0xed, (byte)0x4b};
 
@@ -498,7 +500,7 @@
 
     void wakeupEvent(String iface, int uid, int ether, int ip, byte[] mac, String srcIp,
             String dstIp, int sport, int dport, long now) throws Exception {
-        String prefix = NetdEventListenerService.WAKEUP_EVENT_IFACE_PREFIX + iface;
+        String prefix = NET_HANDLE + ":" + iface;
         mService.onWakeupEvent(prefix, uid, ether, ip, mac, srcIp, dstIp, sport, dport, now);
     }
 
diff --git a/tests/unit/java/com/android/server/connectivity/VpnTest.java b/tests/unit/java/com/android/server/connectivity/VpnTest.java
index 2d87728..1f965d9 100644
--- a/tests/unit/java/com/android/server/connectivity/VpnTest.java
+++ b/tests/unit/java/com/android/server/connectivity/VpnTest.java
@@ -25,6 +25,8 @@
 import static android.net.ConnectivityManager.NetworkCallback;
 import static android.net.INetd.IF_STATE_DOWN;
 import static android.net.INetd.IF_STATE_UP;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
 import static android.net.RouteInfo.RTN_UNREACHABLE;
 import static android.net.VpnManager.TYPE_VPN_PLATFORM;
 import static android.net.cts.util.IkeSessionTestUtils.CHILD_PARAMS;
@@ -36,6 +38,8 @@
 import static android.net.ipsec.ike.IkeSessionParams.ESP_IP_VERSION_AUTO;
 import static android.os.Build.VERSION_CODES.S_V2;
 import static android.os.UserHandle.PER_USER_RANGE;
+import static android.telephony.CarrierConfigManager.KEY_CARRIER_CONFIG_APPLIED_BOOL;
+import static android.telephony.CarrierConfigManager.KEY_MIN_UDP_PORT_4500_NAT_TIMEOUT_SEC_INT;
 
 import static com.android.net.module.util.NetworkStackConstants.IPV6_MIN_MTU;
 import static com.android.server.connectivity.Vpn.AUTOMATIC_KEEPALIVE_DELAY_SECONDS;
@@ -107,6 +111,7 @@
 import android.net.NetworkCapabilities;
 import android.net.NetworkInfo.DetailedState;
 import android.net.RouteInfo;
+import android.net.TelephonyNetworkSpecifier;
 import android.net.UidRangeParcel;
 import android.net.VpnManager;
 import android.net.VpnProfileState;
@@ -126,6 +131,7 @@
 import android.net.ipsec.ike.exceptions.IkeNonProtocolException;
 import android.net.ipsec.ike.exceptions.IkeProtocolException;
 import android.net.ipsec.ike.exceptions.IkeTimeoutException;
+import android.net.wifi.WifiInfo;
 import android.os.Build.VERSION_CODES;
 import android.os.Bundle;
 import android.os.ConditionVariable;
@@ -139,6 +145,10 @@
 import android.os.test.TestLooper;
 import android.provider.Settings;
 import android.security.Credentials;
+import android.telephony.CarrierConfigManager;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Pair;
@@ -265,6 +275,9 @@
     private static final Range<Integer> PRIMARY_USER_RANGE = uidRangeForUser(PRIMARY_USER.id);
     // Same as IkeSessionParams#IKE_NATT_KEEPALIVE_DELAY_SEC_DEFAULT
     private static final int IKE_NATT_KEEPALIVE_DELAY_SEC_DEFAULT = 10;
+    private static final int TEST_KEEPALIVE_TIMER = 800;
+    private static final int TEST_SUB_ID = 1234;
+
     @Mock(answer = Answers.RETURNS_DEEP_STUBS) private Context mContext;
     @Mock private UserManager mUserManager;
     @Mock private PackageManager mPackageManager;
@@ -278,6 +291,10 @@
     @Mock private Vpn.VpnNetworkAgentWrapper mMockNetworkAgent;
     @Mock private ConnectivityManager mConnectivityManager;
     @Mock private ConnectivityDiagnosticsManager mCdm;
+    @Mock private TelephonyManager mTelephonyManager;
+    @Mock private TelephonyManager mTmPerSub;
+    @Mock private CarrierConfigManager mConfigManager;
+    @Mock private SubscriptionManager mSubscriptionManager;
     @Mock private IpSecService mIpSecService;
     @Mock private VpnProfileStore mVpnProfileStore;
     @Mock private ScheduledThreadPoolExecutor mExecutor;
@@ -286,7 +303,6 @@
     private final VpnProfile mVpnProfile;
 
     private IpSecManager mIpSecManager;
-
     private TestDeps mTestDeps;
 
     public VpnTest() throws Exception {
@@ -322,6 +338,11 @@
         mockService(IpSecManager.class, Context.IPSEC_SERVICE, mIpSecManager);
         mockService(ConnectivityDiagnosticsManager.class, Context.CONNECTIVITY_DIAGNOSTICS_SERVICE,
                 mCdm);
+        mockService(TelephonyManager.class, Context.TELEPHONY_SERVICE, mTelephonyManager);
+        mockService(CarrierConfigManager.class, Context.CARRIER_CONFIG_SERVICE, mConfigManager);
+        mockService(SubscriptionManager.class, Context.TELEPHONY_SUBSCRIPTION_SERVICE,
+                mSubscriptionManager);
+        doReturn(mTmPerSub).when(mTelephonyManager).createForSubscriptionId(anyInt());
         when(mContext.getString(R.string.config_customVpnAlwaysOnDisconnectedDialogComponent))
                 .thenReturn(Resources.getSystem().getString(
                         R.string.config_customVpnAlwaysOnDisconnectedDialogComponent));
@@ -1924,7 +1945,7 @@
     }
 
     @Test
-    public void testMigrateIkeSessionFromIkeTunnConnParams_AutoTimerNoTimer()
+    public void testMigrateIkeSession_FromIkeTunnConnParams_AutoTimerNoTimer()
             throws Exception {
         doTestMigrateIkeSession_FromIkeTunnConnParams(
                 false /* isAutomaticIpVersionSelectionEnabled */,
@@ -1933,21 +1954,21 @@
     }
 
     @Test
-    public void testMigrateIkeSessionFromIkeTunnConnParams_AutoTimerTimerSet()
+    public void testMigrateIkeSession_FromIkeTunnConnParams_AutoTimerTimerSet()
             throws Exception {
         doTestMigrateIkeSession_FromIkeTunnConnParams(
                 false /* isAutomaticIpVersionSelectionEnabled */,
                 true /* isAutomaticNattKeepaliveTimerEnabled */,
-                800 /* keepaliveTimeout */);
+                TEST_KEEPALIVE_TIMER);
     }
 
     @Test
-    public void testMigrateIkeSessionFromIkeTunnConnParams_AutoIp()
+    public void testMigrateIkeSession_FromIkeTunnConnParams_AutoIp()
             throws Exception {
         doTestMigrateIkeSession_FromIkeTunnConnParams(
                 true /* isAutomaticIpVersionSelectionEnabled */,
                 false /* isAutomaticNattKeepaliveTimerEnabled */,
-                TEST_KEEPALIVE_TIMEOUT_UNSET /* keepaliveTimeout */);
+                TEST_KEEPALIVE_TIMEOUT_UNSET);
     }
 
     @Test
@@ -2016,9 +2037,12 @@
                 verifySetupPlatformVpn(profile,
                         createIkeConfig(createIkeConnectInfo(), true /* isMobikeEnabled */),
                         false /* mtuSupportsIpv6 */);
-        // Mock new network comes up and the cleanup task is cancelled
+        // Simulate a new network coming up
         vpnSnapShot.nwCb.onAvailable(TEST_NETWORK_2);
+        verify(mIkeSessionWrapper, never()).setNetwork(any(), anyInt(), anyInt(), anyInt());
 
+        vpnSnapShot.nwCb.onCapabilitiesChanged(
+                TEST_NETWORK_2, new NetworkCapabilities.Builder().build());
         // Verify MOBIKE is triggered
         verify(mIkeSessionWrapper).setNetwork(TEST_NETWORK_2,
                 expectedIpVersion, expectedEncapType, expectedKeepalive);
@@ -2026,6 +2050,102 @@
         vpnSnapShot.vpn.mVpnRunner.exitVpnRunner();
     }
 
+    private void mockCarrierConfig(int subId, int keepaliveTimer, int simStatus) {
+        final SubscriptionInfo subscriptionInfo = mock(SubscriptionInfo.class);
+        doReturn(subId).when(subscriptionInfo).getSubscriptionId();
+        doReturn(List.of(subscriptionInfo)).when(mSubscriptionManager)
+                .getActiveSubscriptionInfoList();
+
+        doReturn(simStatus).when(mTmPerSub).getSimApplicationState();
+
+        final PersistableBundle persistableBundle = new PersistableBundle();
+        persistableBundle.putInt(KEY_MIN_UDP_PORT_4500_NAT_TIMEOUT_SEC_INT, keepaliveTimer);
+        // For CarrierConfigManager.isConfigForIdentifiedCarrier check
+        persistableBundle.putBoolean(KEY_CARRIER_CONFIG_APPLIED_BOOL, true);
+        doReturn(persistableBundle).when(mConfigManager).getConfigForSubId(subId);
+    }
+
+    private CarrierConfigManager.CarrierConfigChangeListener getCarrierConfigListener() {
+        final ArgumentCaptor<CarrierConfigManager.CarrierConfigChangeListener> listenerCaptor =
+                ArgumentCaptor.forClass(CarrierConfigManager.CarrierConfigChangeListener.class);
+
+        verify(mConfigManager).registerCarrierConfigChangeListener(any(), listenerCaptor.capture());
+
+        return listenerCaptor.getValue();
+    }
+
+    @Test
+    public void testNattKeepaliveTimerFromCarrierConfig_noSubId() throws Exception {
+        doTestNattKeepaliveTimerFromCarrierConfig(new NetworkCapabilities(),
+                TelephonyManager.SIM_STATE_LOADED, AUTOMATIC_KEEPALIVE_DELAY_SECONDS);
+    }
+
+    @Test
+    public void testNattKeepaliveTimerFromCarrierConfig_simAbsent() throws Exception {
+        doTestNattKeepaliveTimerFromCarrierConfig(new NetworkCapabilities.Builder().build(),
+                TelephonyManager.SIM_STATE_ABSENT, AUTOMATIC_KEEPALIVE_DELAY_SECONDS);
+    }
+
+    @Test
+    public void testNattKeepaliveTimerFromCarrierConfig() throws Exception {
+        final NetworkCapabilities nc = new NetworkCapabilities.Builder()
+                .addTransportType(TRANSPORT_CELLULAR)
+                .setNetworkSpecifier(new TelephonyNetworkSpecifier.Builder()
+                        .setSubscriptionId(TEST_SUB_ID)
+                        .build())
+                .build();
+        doTestNattKeepaliveTimerFromCarrierConfig(nc,
+                TelephonyManager.SIM_STATE_LOADED, TEST_KEEPALIVE_TIMER);
+    }
+
+    @Test
+    public void testNattKeepaliveTimerFromCarrierConfig_NotCell() throws Exception {
+        final NetworkCapabilities nc = new NetworkCapabilities.Builder()
+                .addTransportType(TRANSPORT_WIFI)
+                .setTransportInfo(new WifiInfo.Builder().build())
+                .build();
+        doTestNattKeepaliveTimerFromCarrierConfig(nc,
+                TelephonyManager.SIM_STATE_LOADED, AUTOMATIC_KEEPALIVE_DELAY_SECONDS);
+    }
+
+    private void doTestNattKeepaliveTimerFromCarrierConfig(NetworkCapabilities nc, int simState,
+            int expectedKeepaliveTimer) throws Exception {
+        final Ikev2VpnProfile ikeProfile =
+                new Ikev2VpnProfile.Builder(TEST_VPN_SERVER, TEST_VPN_IDENTITY)
+                        .setAuthPsk(TEST_VPN_PSK)
+                        .setBypassable(true /* isBypassable */)
+                        .setAutomaticNattKeepaliveTimerEnabled(true)
+                        .build();
+
+        final PlatformVpnSnapshot vpnSnapShot =
+                verifySetupPlatformVpn(ikeProfile.toVpnProfile(),
+                        createIkeConfig(createIkeConnectInfo(), true /* isMobikeEnabled */),
+                        false /* mtuSupportsIpv6 */);
+
+        final CarrierConfigManager.CarrierConfigChangeListener listener =
+                getCarrierConfigListener();
+
+        // Simulate a new network coming up
+        vpnSnapShot.nwCb.onAvailable(TEST_NETWORK_2);
+        // Migration will not be started until receiving network capabilities change.
+        verify(mIkeSessionWrapper, never()).setNetwork(any(), anyInt(), anyInt(), anyInt());
+
+        reset(mIkeSessionWrapper);
+        mockCarrierConfig(TEST_SUB_ID, TEST_KEEPALIVE_TIMER, simState);
+        vpnSnapShot.nwCb.onCapabilitiesChanged(TEST_NETWORK_2, nc);
+        verify(mIkeSessionWrapper).setNetwork(TEST_NETWORK_2,
+                ESP_IP_VERSION_AUTO, ESP_ENCAP_TYPE_AUTO, expectedKeepaliveTimer);
+
+        reset(mExecutor);
+        reset(mIkeSessionWrapper);
+
+        // Trigger carrier config change
+        listener.onCarrierConfigChanged(1 /* logicalSlotIndex */, TEST_SUB_ID,
+                -1 /* carrierId */, -1 /* specificCarrierId */);
+        verify(mIkeSessionWrapper).setNetwork(TEST_NETWORK_2,
+                ESP_IP_VERSION_AUTO, ESP_ENCAP_TYPE_AUTO, expectedKeepaliveTimer);
+    }
+
     @Test
     public void testStartPlatformVpn_mtuDoesNotSupportIpv6() throws Exception {
         final PlatformVpnSnapshot vpnSnapShot =
@@ -2051,7 +2171,10 @@
         // Mock new network comes up and the cleanup task is cancelled
         vpnSnapShot.nwCb.onAvailable(TEST_NETWORK_2);
         verify(mScheduledFuture).cancel(anyBoolean());
+        verify(mIkeSessionWrapper, never()).setNetwork(any(), anyInt(), anyInt(), anyInt());
 
+        vpnSnapShot.nwCb.onCapabilitiesChanged(TEST_NETWORK_2,
+                new NetworkCapabilities.Builder().build());
         // Verify MOBIKE is triggered
         verify(mIkeSessionWrapper).setNetwork(eq(TEST_NETWORK_2),
                 eq(ESP_IP_VERSION_AUTO) /* ipVersion */,
@@ -2146,7 +2269,11 @@
         // Mock network switch
         vpnSnapShot.nwCb.onLost(TEST_NETWORK);
         vpnSnapShot.nwCb.onAvailable(TEST_NETWORK_2);
+        // The old IKE Session will not be killed until receiving network capabilities change.
+        verify(mIkeSessionWrapper, never()).kill();
 
+        vpnSnapShot.nwCb.onCapabilitiesChanged(
+                TEST_NETWORK_2, new NetworkCapabilities.Builder().build());
         // Verify the old IKE Session is killed
         verify(mIkeSessionWrapper).kill();
 
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 1febe6d..375c150 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsAdvertiserTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsAdvertiserTest.kt
@@ -42,6 +42,7 @@
 import org.mockito.Mockito.doReturn
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.never
+import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
 
 private const val SERVICE_ID_1 = 1
@@ -51,6 +52,7 @@
 private val TEST_LINKADDR = LinkAddress(TEST_ADDR, 64 /* prefixLength */)
 private val TEST_NETWORK_1 = mock(Network::class.java)
 private val TEST_NETWORK_2 = mock(Network::class.java)
+private val TEST_HOSTNAME = arrayOf("Android_test", "local")
 
 private val SERVICE_1 = NsdServiceInfo("TestServiceName", "_advertisertest._tcp").apply {
     port = 12345
@@ -81,10 +83,13 @@
     @Before
     fun setUp() {
         thread.start()
+        doReturn(TEST_HOSTNAME).`when`(mockDeps).generateHostname()
         doReturn(mockInterfaceAdvertiser1).`when`(mockDeps).makeAdvertiser(eq(mockSocket1),
-                any(), any(), any(), any())
+                any(), any(), any(), any(), eq(TEST_HOSTNAME)
+        )
         doReturn(mockInterfaceAdvertiser2).`when`(mockDeps).makeAdvertiser(eq(mockSocket2),
-                any(), any(), any(), any())
+                any(), any(), any(), any(), eq(TEST_HOSTNAME)
+        )
         doReturn(true).`when`(mockInterfaceAdvertiser1).isProbing(anyInt())
         doReturn(true).`when`(mockInterfaceAdvertiser2).isProbing(anyInt())
     }
@@ -106,8 +111,14 @@
         postSync { socketCb.onSocketCreated(TEST_NETWORK_1, mockSocket1, listOf(TEST_LINKADDR)) }
 
         val intAdvCbCaptor = ArgumentCaptor.forClass(MdnsInterfaceAdvertiser.Callback::class.java)
-        verify(mockDeps).makeAdvertiser(eq(mockSocket1),
-                eq(listOf(TEST_LINKADDR)), eq(thread.looper), any(), intAdvCbCaptor.capture())
+        verify(mockDeps).makeAdvertiser(
+            eq(mockSocket1),
+            eq(listOf(TEST_LINKADDR)),
+            eq(thread.looper),
+            any(),
+            intAdvCbCaptor.capture(),
+            eq(TEST_HOSTNAME)
+        )
 
         doReturn(false).`when`(mockInterfaceAdvertiser1).isProbing(SERVICE_ID_1)
         postSync { intAdvCbCaptor.value.onRegisterServiceSucceeded(
@@ -134,9 +145,11 @@
         val intAdvCbCaptor1 = ArgumentCaptor.forClass(MdnsInterfaceAdvertiser.Callback::class.java)
         val intAdvCbCaptor2 = ArgumentCaptor.forClass(MdnsInterfaceAdvertiser.Callback::class.java)
         verify(mockDeps).makeAdvertiser(eq(mockSocket1), eq(listOf(TEST_LINKADDR)),
-                eq(thread.looper), any(), intAdvCbCaptor1.capture())
+                eq(thread.looper), any(), intAdvCbCaptor1.capture(), eq(TEST_HOSTNAME)
+        )
         verify(mockDeps).makeAdvertiser(eq(mockSocket2), eq(listOf(TEST_LINKADDR)),
-                eq(thread.looper), any(), intAdvCbCaptor2.capture())
+                eq(thread.looper), any(), intAdvCbCaptor2.capture(), eq(TEST_HOSTNAME)
+        )
 
         doReturn(false).`when`(mockInterfaceAdvertiser1).isProbing(SERVICE_ID_1)
         postSync { intAdvCbCaptor1.value.onRegisterServiceSucceeded(
@@ -192,7 +205,8 @@
 
         val intAdvCbCaptor = ArgumentCaptor.forClass(MdnsInterfaceAdvertiser.Callback::class.java)
         verify(mockDeps).makeAdvertiser(eq(mockSocket1), eq(listOf(TEST_LINKADDR)),
-                eq(thread.looper), any(), intAdvCbCaptor.capture())
+                eq(thread.looper), any(), intAdvCbCaptor.capture(), eq(TEST_HOSTNAME)
+        )
         verify(mockInterfaceAdvertiser1).addService(eq(SERVICE_ID_1),
                 argThat { it.matches(SERVICE_1) })
         verify(mockInterfaceAdvertiser1).addService(eq(SERVICE_ID_2),
@@ -216,6 +230,15 @@
         verify(mockInterfaceAdvertiser1, atLeastOnce()).destroyNow()
     }
 
+    @Test
+    fun testRemoveService_whenAllServiceRemoved_thenUpdateHostName() {
+        val advertiser = MdnsAdvertiser(thread.looper, socketProvider, cb, mockDeps)
+        verify(mockDeps, times(1)).generateHostname()
+        postSync { advertiser.addService(SERVICE_ID_1, SERVICE_1) }
+        postSync { advertiser.removeService(SERVICE_ID_1) }
+        verify(mockDeps, times(2)).generateHostname()
+    }
+
     private fun postSync(r: () -> Unit) {
         handler.post(r)
         handler.waitForIdle(TIMEOUT_MS)
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsDiscoveryManagerTests.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsDiscoveryManagerTests.java
index 83e7696..7e7e6a4 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsDiscoveryManagerTests.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsDiscoveryManagerTests.java
@@ -18,24 +18,33 @@
 
 import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
 
-import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.Network;
 import android.text.TextUtils;
+import android.util.Pair;
 
+import com.android.server.connectivity.mdns.MdnsSocketClientBase.SocketCreationCallback;
 import com.android.testutils.DevSdkIgnoreRule;
 import com.android.testutils.DevSdkIgnoreRunner;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
 import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
+import java.util.List;
 
 /** Tests for {@link MdnsDiscoveryManager}. */
 @RunWith(DevSdkIgnoreRunner.class)
@@ -44,6 +53,10 @@
 
     private static final String SERVICE_TYPE_1 = "_googlecast._tcp.local";
     private static final String SERVICE_TYPE_2 = "_test._tcp.local";
+    private static final Pair<String, Network> PER_NETWORK_SERVICE_TYPE_1 =
+            Pair.create(SERVICE_TYPE_1, null);
+    private static final Pair<String, Network> PER_NETWORK_SERVICE_TYPE_2 =
+            Pair.create(SERVICE_TYPE_2, null);
 
     @Mock private ExecutorProvider executorProvider;
     @Mock private MdnsSocketClientBase socketClient;
@@ -65,10 +78,13 @@
 
         discoveryManager = new MdnsDiscoveryManager(executorProvider, socketClient) {
                     @Override
-                    MdnsServiceTypeClient createServiceTypeClient(@NonNull String serviceType) {
-                        if (serviceType.equals(SERVICE_TYPE_1)) {
+                    MdnsServiceTypeClient createServiceTypeClient(@NonNull String serviceType,
+                            @Nullable Network network) {
+                        final Pair<String, Network> perNetworkServiceType =
+                                Pair.create(serviceType, network);
+                        if (perNetworkServiceType.equals(PER_NETWORK_SERVICE_TYPE_1)) {
                             return mockServiceTypeClientOne;
-                        } else if (serviceType.equals(SERVICE_TYPE_2)) {
+                        } else if (perNetworkServiceType.equals(PER_NETWORK_SERVICE_TYPE_2)) {
                             return mockServiceTypeClientTwo;
                         }
                         return null;
@@ -76,13 +92,23 @@
                 };
     }
 
+    private void verifyListenerRegistration(String serviceType, MdnsServiceBrowserListener listener,
+            MdnsServiceTypeClient client) throws IOException {
+        final ArgumentCaptor<SocketCreationCallback> callbackCaptor =
+                ArgumentCaptor.forClass(SocketCreationCallback.class);
+        discoveryManager.registerListener(serviceType, listener,
+                MdnsSearchOptions.getDefaultOptions());
+        verify(socketClient).startDiscovery();
+        verify(socketClient).notifyNetworkRequested(
+                eq(listener), any(), callbackCaptor.capture());
+        final SocketCreationCallback callback = callbackCaptor.getValue();
+        callback.onSocketCreated(null /* network */);
+        verify(client).startSendAndReceive(listener, MdnsSearchOptions.getDefaultOptions());
+    }
+
     @Test
     public void registerListener_unregisterListener() throws IOException {
-        discoveryManager.registerListener(
-                SERVICE_TYPE_1, mockListenerOne, MdnsSearchOptions.getDefaultOptions());
-        verify(socketClient).startDiscovery();
-        verify(mockServiceTypeClientOne)
-                .startSendAndReceive(mockListenerOne, MdnsSearchOptions.getDefaultOptions());
+        verifyListenerRegistration(SERVICE_TYPE_1, mockListenerOne, mockServiceTypeClientOne);
 
         when(mockServiceTypeClientOne.stopSendAndReceive(mockListenerOne)).thenReturn(true);
         discoveryManager.unregisterListener(SERVICE_TYPE_1, mockListenerOne);
@@ -92,44 +118,47 @@
 
     @Test
     public void registerMultipleListeners() throws IOException {
-        discoveryManager.registerListener(
-                SERVICE_TYPE_1, mockListenerOne, MdnsSearchOptions.getDefaultOptions());
-        verify(socketClient).startDiscovery();
-        verify(mockServiceTypeClientOne)
-                .startSendAndReceive(mockListenerOne, MdnsSearchOptions.getDefaultOptions());
-
-        discoveryManager.registerListener(
-                SERVICE_TYPE_2, mockListenerTwo, MdnsSearchOptions.getDefaultOptions());
-        verify(mockServiceTypeClientTwo)
-                .startSendAndReceive(mockListenerTwo, MdnsSearchOptions.getDefaultOptions());
+        verifyListenerRegistration(SERVICE_TYPE_1, mockListenerOne, mockServiceTypeClientOne);
+        verifyListenerRegistration(SERVICE_TYPE_2, mockListenerTwo, mockServiceTypeClientTwo);
     }
 
     @Test
-    public void onResponseReceived() {
-        discoveryManager.registerListener(
-                SERVICE_TYPE_1, mockListenerOne, MdnsSearchOptions.getDefaultOptions());
-        discoveryManager.registerListener(
-                SERVICE_TYPE_2, mockListenerTwo, MdnsSearchOptions.getDefaultOptions());
+    public void onResponseReceived() throws IOException {
+        verifyListenerRegistration(SERVICE_TYPE_1, mockListenerOne, mockServiceTypeClientOne);
+        verifyListenerRegistration(SERVICE_TYPE_2, mockListenerTwo, mockServiceTypeClientTwo);
 
-        MdnsResponse responseForServiceTypeOne = createMockResponse(SERVICE_TYPE_1);
-        discoveryManager.onResponseReceived(responseForServiceTypeOne);
-        verify(mockServiceTypeClientOne).processResponse(responseForServiceTypeOne);
+        MdnsPacket responseForServiceTypeOne = createMdnsPacket(SERVICE_TYPE_1);
+        final int ifIndex = 1;
+        discoveryManager.onResponseReceived(responseForServiceTypeOne, ifIndex, null /* network */);
+        verify(mockServiceTypeClientOne).processResponse(responseForServiceTypeOne, ifIndex,
+                null /* network */);
 
-        MdnsResponse responseForServiceTypeTwo = createMockResponse(SERVICE_TYPE_2);
-        discoveryManager.onResponseReceived(responseForServiceTypeTwo);
-        verify(mockServiceTypeClientTwo).processResponse(responseForServiceTypeTwo);
+        MdnsPacket responseForServiceTypeTwo = createMdnsPacket(SERVICE_TYPE_2);
+        discoveryManager.onResponseReceived(responseForServiceTypeTwo, ifIndex, null /* network */);
+        verify(mockServiceTypeClientTwo).processResponse(responseForServiceTypeTwo, ifIndex,
+                null /* network */);
 
-        MdnsResponse responseForSubtype = createMockResponse("subtype._sub._googlecast._tcp.local");
-        discoveryManager.onResponseReceived(responseForSubtype);
-        verify(mockServiceTypeClientOne).processResponse(responseForSubtype);
+        MdnsPacket responseForSubtype = createMdnsPacket("subtype._sub._googlecast._tcp.local");
+        discoveryManager.onResponseReceived(responseForSubtype, ifIndex, null /* network */);
+        verify(mockServiceTypeClientOne).processResponse(responseForSubtype, ifIndex,
+                null /* network */);
     }
 
-    private MdnsResponse createMockResponse(String serviceType) {
-        MdnsPointerRecord mockPointerRecord = mock(MdnsPointerRecord.class);
-        MdnsResponse mockResponse = mock(MdnsResponse.class);
-        when(mockResponse.getPointerRecords())
-                .thenReturn(Collections.singletonList(mockPointerRecord));
-        when(mockPointerRecord.getName()).thenReturn(TextUtils.split(serviceType, "\\."));
-        return mockResponse;
+    private MdnsPacket createMdnsPacket(String serviceType) {
+        final String[] type = TextUtils.split(serviceType, "\\.");
+        final ArrayList<String> name = new ArrayList<>(type.length + 1);
+        name.add("TestName");
+        name.addAll(Arrays.asList(type));
+        return new MdnsPacket(0 /* flags */,
+                Collections.emptyList() /* questions */,
+                List.of(new MdnsPointerRecord(
+                        type,
+                        0L /* receiptTimeMillis */,
+                        false /* cacheFlush */,
+                        120000 /* ttlMillis */,
+                        name.toArray(new String[0])
+                        )) /* answers */,
+                Collections.emptyList() /* authorityRecords */,
+                Collections.emptyList() /* additionalRecords */);
     }
 }
\ No newline at end of file
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 4a806b1..2d8d8f3 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiserTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiserTest.kt
@@ -55,6 +55,7 @@
 
 private val TEST_ADDRS = listOf(LinkAddress(parseNumericAddress("2001:db8::123"), 64))
 private val TEST_BUFFER = ByteArray(1300)
+private val TEST_HOSTNAME = arrayOf("Android_test", "local")
 
 private const val TEST_SERVICE_ID_1 = 42
 private val TEST_SERVICE_1 = NsdServiceInfo().apply {
@@ -88,12 +89,23 @@
     private val packetHandler get() = packetHandlerCaptor.value
 
     private val advertiser by lazy {
-        MdnsInterfaceAdvertiser(LOG_TAG, socket, TEST_ADDRS, thread.looper, TEST_BUFFER, cb, deps)
+        MdnsInterfaceAdvertiser(
+            LOG_TAG,
+            socket,
+            TEST_ADDRS,
+            thread.looper,
+            TEST_BUFFER,
+            cb,
+            deps,
+            TEST_HOSTNAME
+        )
     }
 
     @Before
     fun setUp() {
-        doReturn(repository).`when`(deps).makeRecordRepository(any())
+        doReturn(repository).`when`(deps).makeRecordRepository(any(),
+            eq(TEST_HOSTNAME)
+        )
         doReturn(replySender).`when`(deps).makeReplySender(anyString(), any(), any(), any())
         doReturn(announcer).`when`(deps).makeMdnsAnnouncer(anyString(), any(), any(), any())
         doReturn(prober).`when`(deps).makeMdnsProber(anyString(), any(), any(), any())
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClientTest.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClientTest.java
index 9d42a65..90c43e5 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClientTest.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClientTest.java
@@ -19,9 +19,9 @@
 import static com.android.server.connectivity.mdns.MdnsSocketProvider.SocketCallback;
 import static com.android.server.connectivity.mdns.MulticastPacketReader.PacketHandler;
 
-import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.timeout;
@@ -62,6 +62,7 @@
     @Mock private MdnsInterfaceSocket mSocket;
     @Mock private MdnsServiceBrowserListener mListener;
     @Mock private MdnsSocketClientBase.Callback mCallback;
+    @Mock private MdnsSocketClientBase.SocketCreationCallback mSocketCreationCallback;
     private MdnsMultinetworkSocketClient mSocketClient;
     private Handler mHandler;
 
@@ -78,7 +79,8 @@
     private SocketCallback expectSocketCallback() {
         final ArgumentCaptor<SocketCallback> callbackCaptor =
                 ArgumentCaptor.forClass(SocketCallback.class);
-        mHandler.post(() -> mSocketClient.notifyNetworkRequested(mListener, mNetwork));
+        mHandler.post(() -> mSocketClient.notifyNetworkRequested(
+                mListener, mNetwork, mSocketCreationCallback));
         verify(mProvider, timeout(DEFAULT_TIMEOUT))
                 .requestSocket(eq(mNetwork), callbackCaptor.capture());
         return callbackCaptor.getValue();
@@ -107,6 +109,7 @@
         doReturn(createEmptyNetworkInterface()).when(mSocket).getInterface();
         // Notify socket created
         callback.onSocketCreated(mNetwork, mSocket, List.of());
+        verify(mSocketCreationCallback).onSocketCreated(mNetwork);
 
         // Send packet to IPv4 with target network and verify sending has been called.
         mSocketClient.sendMulticastPacket(ipv4Packet, mNetwork);
@@ -138,6 +141,7 @@
         doReturn(createEmptyNetworkInterface()).when(mSocket).getInterface();
         // Notify socket created
         callback.onSocketCreated(mNetwork, mSocket, List.of());
+        verify(mSocketCreationCallback).onSocketCreated(mNetwork);
 
         final ArgumentCaptor<PacketHandler> handlerCaptor =
                 ArgumentCaptor.forClass(PacketHandler.class);
@@ -146,16 +150,23 @@
         // Send the data and verify the received records.
         final PacketHandler handler = handlerCaptor.getValue();
         handler.handlePacket(data, data.length, null /* src */);
-        final ArgumentCaptor<MdnsResponse> responseCaptor =
-                ArgumentCaptor.forClass(MdnsResponse.class);
-        verify(mCallback).onResponseReceived(responseCaptor.capture());
-        final MdnsResponse response = responseCaptor.getValue();
-        assertTrue(response.hasPointerRecords());
-        assertArrayEquals("_testtype._tcp.local".split("\\."),
-                response.getPointerRecords().get(0).getName());
-        assertTrue(response.hasServiceRecord());
-        assertEquals("testservice", response.getServiceRecord().getServiceInstanceName());
-        assertEquals("Android.local".split("\\."),
-                response.getServiceRecord().getServiceHost());
+        final ArgumentCaptor<MdnsPacket> responseCaptor =
+                ArgumentCaptor.forClass(MdnsPacket.class);
+        verify(mCallback).onResponseReceived(responseCaptor.capture(), anyInt(), any());
+        final MdnsPacket response = responseCaptor.getValue();
+        assertEquals(0, response.questions.size());
+        assertEquals(0, response.additionalRecords.size());
+        assertEquals(0, response.authorityRecords.size());
+
+        final String[] serviceName = "testservice._testtype._tcp.local".split("\\.");
+        assertEquals(List.of(
+                new MdnsPointerRecord("_testtype._tcp.local".split("\\."),
+                        0L /* receiptTimeMillis */, false /* cacheFlush */, 4500000 /* ttlMillis */,
+                        serviceName),
+                new MdnsServiceRecord(serviceName, 0L /* receiptTimeMillis */,
+                        false /* cacheFlush */, 4500000 /* ttlMillis */, 0 /* servicePriority */,
+                        0 /* serviceWeight */, 31234 /* servicePort */,
+                        new String[] { "Android", "local" } /* serviceHost */)
+        ), response.answers);
     }
 }
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 ecc11ec..5665091 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsRecordRepositoryTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsRecordRepositoryTest.kt
@@ -67,7 +67,6 @@
 class MdnsRecordRepositoryTest {
     private val thread = HandlerThread(MdnsRecordRepositoryTest::class.simpleName)
     private val deps = object : Dependencies() {
-        override fun getHostname() = TEST_HOSTNAME
         override fun getInterfaceInetAddresses(iface: NetworkInterface) =
                 Collections.enumeration(TEST_ADDRESSES.map { it.address })
     }
@@ -84,7 +83,7 @@
 
     @Test
     fun testAddServiceAndProbe() {
-        val repository = MdnsRecordRepository(thread.looper, deps)
+        val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME)
         assertEquals(0, repository.servicesCount)
         assertEquals(-1, repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1))
         assertEquals(1, repository.servicesCount)
@@ -117,7 +116,7 @@
 
     @Test
     fun testAddAndConflicts() {
-        val repository = MdnsRecordRepository(thread.looper, deps)
+        val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME)
         repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1)
         assertFailsWith(NameConflictException::class) {
             repository.addService(TEST_SERVICE_ID_2, TEST_SERVICE_1)
@@ -126,7 +125,7 @@
 
     @Test
     fun testInvalidReuseOfServiceId() {
-        val repository = MdnsRecordRepository(thread.looper, deps)
+        val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME)
         repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1)
         assertFailsWith(IllegalArgumentException::class) {
             repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_2)
@@ -135,7 +134,7 @@
 
     @Test
     fun testHasActiveService() {
-        val repository = MdnsRecordRepository(thread.looper, deps)
+        val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME)
         assertFalse(repository.hasActiveService(TEST_SERVICE_ID_1))
 
         repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1)
@@ -152,7 +151,7 @@
 
     @Test
     fun testExitAnnouncements() {
-        val repository = MdnsRecordRepository(thread.looper, deps)
+        val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME)
         repository.initWithService(TEST_SERVICE_ID_1, TEST_SERVICE_1)
         repository.onAdvertisementSent(TEST_SERVICE_ID_1)
 
@@ -181,7 +180,7 @@
 
     @Test
     fun testExitingServiceReAdded() {
-        val repository = MdnsRecordRepository(thread.looper, deps)
+        val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME)
         repository.initWithService(TEST_SERVICE_ID_1, TEST_SERVICE_1)
         repository.onAdvertisementSent(TEST_SERVICE_ID_1)
         repository.exitService(TEST_SERVICE_ID_1)
@@ -195,7 +194,7 @@
 
     @Test
     fun testOnProbingSucceeded() {
-        val repository = MdnsRecordRepository(thread.looper, deps)
+        val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME)
         val announcementInfo = repository.initWithService(TEST_SERVICE_ID_1, TEST_SERVICE_1)
         repository.onAdvertisementSent(TEST_SERVICE_ID_1)
         val packet = announcementInfo.getPacket(0)
@@ -319,7 +318,7 @@
 
     @Test
     fun testGetReply() {
-        val repository = MdnsRecordRepository(thread.looper, deps)
+        val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME)
         repository.initWithService(TEST_SERVICE_ID_1, TEST_SERVICE_1)
         val questions = listOf(MdnsPointerRecord(arrayOf("_testservice", "_tcp", "local"),
                 0L /* receiptTimeMillis */,
@@ -404,7 +403,7 @@
 
     @Test
     fun testGetConflictingServices() {
-        val repository = MdnsRecordRepository(thread.looper, deps)
+        val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME)
         repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1)
         repository.addService(TEST_SERVICE_ID_2, TEST_SERVICE_2)
 
@@ -432,7 +431,7 @@
 
     @Test
     fun testGetConflictingServices_IdenticalService() {
-        val repository = MdnsRecordRepository(thread.looper, deps)
+        val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME)
         repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1)
         repository.addService(TEST_SERVICE_ID_2, TEST_SERVICE_2)
 
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 4cae447..a80c078 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsResponseDecoderTests.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsResponseDecoderTests.java
@@ -16,20 +16,26 @@
 
 package com.android.server.connectivity.mdns;
 
+import static android.net.InetAddresses.parseNumericAddress;
+
 import static com.android.server.connectivity.mdns.MdnsResponseDecoder.Clock;
 import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
 
+import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 import static org.mockito.Mockito.mock;
 
-import android.net.InetAddresses;
 import android.net.Network;
+import android.util.ArraySet;
 
 import com.android.net.module.util.HexDump;
+import com.android.server.connectivity.mdns.MdnsResponseTests.MdnsInet4AddressRecord;
+import com.android.server.connectivity.mdns.MdnsResponseTests.MdnsInet6AddressRecord;
 import com.android.testutils.DevSdkIgnoreRule;
 import com.android.testutils.DevSdkIgnoreRunner;
 
@@ -43,8 +49,11 @@
 import java.net.Inet4Address;
 import java.net.Inet6Address;
 import java.net.InetSocketAddress;
-import java.util.LinkedList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
 import java.util.List;
+import java.util.stream.Collectors;
 
 @RunWith(DevSdkIgnoreRunner.class)
 @DevSdkIgnoreRule.IgnoreUpTo(SC_V2)
@@ -147,6 +156,44 @@
             + "010001000000780004C0A8018A0000000000000000000000000000"
             + "000000");
 
+    // MDNS record for name "testhost1" with an IPv4 address of 10.1.2.3
+    private static final byte[] DATAIN_IPV4_1 = HexDump.hexStringToByteArray(
+            "0974657374686f73743100000180010000007800040a010203");
+    // MDNS record for name "testhost1" with an IPv4 address of 10.1.2.4
+    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
+    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
+    private static final byte[] DATAIN_IPV6_2 = HexDump.hexStringToByteArray(
+            "0974657374686f73743100001c8001000000780010aabbccdd11223344a0b0c0d010203030");
+    // MDNS record w/name "test" & PTR to foo.bar.quxx
+    private static final byte[] DATAIN_PTR_1 = HexDump.hexStringToByteArray(
+            "047465737400000C000100001194000E03666F6F03626172047175787800");
+    // MDNS record w/name "test" & PTR to foo.bar.quxy
+    private static final byte[] DATAIN_PTR_2 = HexDump.hexStringToByteArray(
+            "047465737400000C000100001194000E03666F6F03626172047175787900");
+    // SRV record for: scapy.DNSRRSRV(rrname='foo.bar.quxx', ttl=120, port=1234, target='testhost1')
+    private static final byte[] DATAIN_SERVICE_1 = HexDump.hexStringToByteArray(
+            "03666f6f03626172047175787800002100010000007800110000000004d20974657374686f73743100");
+    // SRV record for: scapy.DNSRRSRV(rrname='foo.bar.quxx', ttl=120, port=1234, target='testhost2')
+    private static final byte[] DATAIN_SERVICE_2 = HexDump.hexStringToByteArray(
+            "03666f6f03626172047175787800002100010000007800110000000004d20974657374686f73743200");
+    // TXT record for: scapy.DNSRR(rrname='foo.bar.quxx', type='TXT', ttl=120,
+    //     rdata=[b'a=hello there', b'b=1234567890', b'xyz=!$$$'])
+    private static final byte[] DATAIN_TEXT_1 = HexDump.hexStringToByteArray(
+            "03666f6f03626172047175787800001000010000007800240d613d68656c6c6f2074686572650c623d3132"
+                    + "33343536373839300878797a3d21242424");
+
+    // TXT record for: scapy.DNSRR(rrname='foo.bar.quxx', type='TXT', ttl=120,
+    //     rdata=[b'a=hello there', b'b=1234567890', b'xyz=!$$$'])
+    private static final byte[] DATAIN_TEXT_2 = HexDump.hexStringToByteArray(
+            "03666f6f03626172047175787800001000010000007800240d613d68656c6c6f2074686572650c623d3132"
+                    + "33343536373839300878797a3d21402324");
+
+    private static final String[] DATAIN_SERVICE_NAME_1 = new String[] { "foo", "bar", "quxx" };
+
     private static final String CAST_SERVICE_NAME = "_googlecast";
     private static final String[] CAST_SERVICE_TYPE =
             new String[] {CAST_SERVICE_NAME, "_tcp", "local"};
@@ -154,41 +201,28 @@
     private static final String[] MATTER_SERVICE_TYPE =
             new String[] {MATTER_SERVICE_NAME, "_tcp", "local"};
 
-    private final List<MdnsResponse> responses = new LinkedList<>();
+    private ArraySet<MdnsResponse> responses;
 
     private final Clock mClock = mock(Clock.class);
 
     @Before
-    public void setUp() {
+    public void setUp() throws Exception {
         MdnsResponseDecoder decoder = new MdnsResponseDecoder(mClock, CAST_SERVICE_TYPE);
         assertNotNull(data);
-        DatagramPacket packet = new DatagramPacket(data, data.length);
-        packet.setSocketAddress(
-                new InetSocketAddress(MdnsConstants.getMdnsIPv4Address(), MdnsConstants.MDNS_PORT));
-        responses.clear();
-        int errorCode = decoder.decode(
-                packet, responses, MdnsSocket.INTERFACE_INDEX_UNSPECIFIED, mock(Network.class));
-        assertEquals(MdnsResponseDecoder.SUCCESS, errorCode);
+        responses = decode(decoder, data);
         assertEquals(1, responses.size());
     }
 
     @Test
-    public void testDecodeWithNullServiceType() {
+    public void testDecodeWithNullServiceType() throws Exception {
         MdnsResponseDecoder decoder = new MdnsResponseDecoder(mClock, null);
-        assertNotNull(data);
-        DatagramPacket packet = new DatagramPacket(data, data.length);
-        packet.setSocketAddress(
-                new InetSocketAddress(MdnsConstants.getMdnsIPv4Address(), MdnsConstants.MDNS_PORT));
-        responses.clear();
-        int errorCode = decoder.decode(
-                packet, responses, MdnsSocket.INTERFACE_INDEX_UNSPECIFIED, mock(Network.class));
-        assertEquals(MdnsResponseDecoder.SUCCESS, errorCode);
+        responses = decode(decoder, data);
         assertEquals(2, responses.size());
     }
 
     @Test
     public void testDecodeMultipleAnswerPacket() throws IOException {
-        MdnsResponse response = responses.get(0);
+        MdnsResponse response = responses.valueAt(0);
         assertTrue(response.isComplete());
 
         MdnsInetAddressRecord inet4AddressRecord = response.getInet4AddressRecord();
@@ -235,16 +269,10 @@
     public void testDecodeIPv6AnswerPacket() throws IOException {
         MdnsResponseDecoder decoder = new MdnsResponseDecoder(mClock, CAST_SERVICE_TYPE);
         assertNotNull(data6);
-        DatagramPacket packet = new DatagramPacket(data6, data6.length);
-        packet.setSocketAddress(
-                new InetSocketAddress(MdnsConstants.getMdnsIPv6Address(), MdnsConstants.MDNS_PORT));
 
-        responses.clear();
-        int errorCode = decoder.decode(
-                packet, responses, MdnsSocket.INTERFACE_INDEX_UNSPECIFIED, mock(Network.class));
-        assertEquals(MdnsResponseDecoder.SUCCESS, errorCode);
-
-        MdnsResponse response = responses.get(0);
+        responses = decode(decoder, data6);
+        assertEquals(1, responses.size());
+        MdnsResponse response = responses.valueAt(0);
         assertTrue(response.isComplete());
 
         MdnsInetAddressRecord inet6AddressRecord = response.getInet6AddressRecord();
@@ -259,104 +287,297 @@
 
     @Test
     public void testIsComplete() {
-        MdnsResponse response = responses.get(0);
+        MdnsResponse response = new MdnsResponse(responses.valueAt(0));
         assertTrue(response.isComplete());
 
         response.clearPointerRecords();
+        // The service name is still known in MdnsResponse#getServiceName
+        assertTrue(response.isComplete());
+
+        response = new MdnsResponse(responses.valueAt(0));
+        response.clearInet4AddressRecords();
         assertFalse(response.isComplete());
 
-        response = responses.get(0);
-        response.setInet4AddressRecord(null);
+        response.addInet6AddressRecord(new MdnsInetAddressRecord(new String[] { "testhostname" },
+                0L /* receiptTimeMillis */, false /* cacheFlush */, 1234L /* ttlMillis */,
+                parseNumericAddress("2008:db1::123")));
+        assertTrue(response.isComplete());
+
+        response.clearInet6AddressRecords();
         assertFalse(response.isComplete());
 
-        response = responses.get(0);
-        response.setInet6AddressRecord(null);
-        assertFalse(response.isComplete());
-
-        response = responses.get(0);
+        response = new MdnsResponse(responses.valueAt(0));
         response.setServiceRecord(null);
         assertFalse(response.isComplete());
 
-        response = responses.get(0);
+        response = new MdnsResponse(responses.valueAt(0));
         response.setTextRecord(null);
         assertFalse(response.isComplete());
     }
 
     @Test
-    public void decode_withInterfaceIndex_populatesInterfaceIndex() {
+    public void decode_withInterfaceIndex_populatesInterfaceIndex() throws Exception {
         MdnsResponseDecoder decoder = new MdnsResponseDecoder(mClock, CAST_SERVICE_TYPE);
         assertNotNull(data6);
         DatagramPacket packet = new DatagramPacket(data6, data6.length);
         packet.setSocketAddress(
                 new InetSocketAddress(MdnsConstants.getMdnsIPv6Address(), MdnsConstants.MDNS_PORT));
 
-        responses.clear();
+        final MdnsPacket parsedPacket = MdnsResponseDecoder.parseResponse(data6, data6.length);
+        assertNotNull(parsedPacket);
+
         final Network network = mock(Network.class);
-        int errorCode = decoder.decode(
-                packet, responses, /* interfaceIndex= */ 10, network);
-        assertEquals(errorCode, MdnsResponseDecoder.SUCCESS);
+        responses = decoder.augmentResponses(parsedPacket,
+                /* existingResponses= */ Collections.emptyList(),
+                /* interfaceIndex= */ 10, network /* expireOnExit= */);
+
         assertEquals(responses.size(), 1);
-        assertEquals(responses.get(0).getInterfaceIndex(), 10);
-        assertEquals(network, responses.get(0).getNetwork());
+        assertEquals(responses.valueAt(0).getInterfaceIndex(), 10);
+        assertEquals(network, responses.valueAt(0).getNetwork());
     }
 
     @Test
-    public void decode_singleHostname_multipleSrvRecords_flagEnabled_multipleCompleteResponses() {
+    public void decode_singleHostname_multipleSrvRecords_flagEnabled_multipleCompleteResponses()
+            throws Exception {
         //MdnsScannerConfigsFlagsImpl.allowMultipleSrvRecordsPerHost.override(true);
         MdnsResponseDecoder decoder = new MdnsResponseDecoder(mClock, MATTER_SERVICE_TYPE);
         assertNotNull(matterDuplicateHostname);
 
-        DatagramPacket packet =
-                new DatagramPacket(matterDuplicateHostname, matterDuplicateHostname.length);
-
-        packet.setSocketAddress(
-                new InetSocketAddress(MdnsConstants.getMdnsIPv6Address(), MdnsConstants.MDNS_PORT));
-
-        responses.clear();
-        int errorCode = decoder.decode(
-                packet, responses, /* interfaceIndex= */ 0, mock(Network.class));
-        assertEquals(MdnsResponseDecoder.SUCCESS, errorCode);
+        responses = decode(decoder, matterDuplicateHostname);
 
         // This should emit two records:
         assertEquals(2, responses.size());
 
-        MdnsResponse response1 = responses.get(0);
-        MdnsResponse response2 = responses.get(0);
+        MdnsResponse response1 = responses.valueAt(0);
+        MdnsResponse response2 = responses.valueAt(0);
 
         // Both of which are complete:
         assertTrue(response1.isComplete());
         assertTrue(response2.isComplete());
 
         // And should both have the same IPv6 address:
-        assertEquals(InetAddresses.parseNumericAddress("2605:a601:a846:5700:3e61:5ff:fe0c:89f8"),
-                response1.getInet6AddressRecord().getInet6Address());
-        assertEquals(InetAddresses.parseNumericAddress("2605:a601:a846:5700:3e61:5ff:fe0c:89f8"),
-                response2.getInet6AddressRecord().getInet6Address());
+        assertTrue(response1.getInet6AddressRecords().stream().anyMatch(
+                record -> record.getInet6Address().equals(
+                        parseNumericAddress("2605:a601:a846:5700:3e61:5ff:fe0c:89f8"))));
+        assertTrue(response2.getInet6AddressRecords().stream().anyMatch(
+                record -> record.getInet6Address().equals(
+                        parseNumericAddress("2605:a601:a846:5700:3e61:5ff:fe0c:89f8"))));
     }
 
     @Test
     @Ignore("MdnsConfigs is not configurable currently.")
-    public void decode_singleHostname_multipleSrvRecords_flagDisabled_singleCompleteResponse() {
+    public void decode_singleHostname_multipleSrvRecords_flagDisabled_singleCompleteResponse()
+            throws Exception {
         //MdnsScannerConfigsFlagsImpl.allowMultipleSrvRecordsPerHost.override(false);
         MdnsResponseDecoder decoder = new MdnsResponseDecoder(mClock, MATTER_SERVICE_TYPE);
         assertNotNull(matterDuplicateHostname);
 
-        DatagramPacket packet =
-                new DatagramPacket(matterDuplicateHostname, matterDuplicateHostname.length);
-
-        packet.setSocketAddress(
-                new InetSocketAddress(MdnsConstants.getMdnsIPv6Address(), MdnsConstants.MDNS_PORT));
-
-        responses.clear();
-        int errorCode = decoder.decode(
-                packet, responses, /* interfaceIndex= */ 0, mock(Network.class));
-        assertEquals(MdnsResponseDecoder.SUCCESS, errorCode);
+        responses = decode(decoder, matterDuplicateHostname);
 
         // This should emit only two records:
         assertEquals(2, responses.size());
 
         // But only the first is complete:
-        assertTrue(responses.get(0).isComplete());
-        assertFalse(responses.get(1).isComplete());
+        assertTrue(responses.valueAt(0).isComplete());
+        assertFalse(responses.valueAt(1).isComplete());
+    }
+
+    @Test
+    public void testDecodeWithIpv4AddressChange() throws IOException {
+        MdnsResponse response = makeMdnsResponse(0, DATAIN_SERVICE_NAME_1, List.of(
+                new PacketAndRecordClass(DATAIN_PTR_1,
+                        MdnsPointerRecord.class),
+                new PacketAndRecordClass(DATAIN_SERVICE_1,
+                        MdnsServiceRecord.class),
+                new PacketAndRecordClass(DATAIN_IPV4_1,
+                        MdnsInet4AddressRecord.class)));
+        // Now update the response with another address
+        final MdnsResponseDecoder decoder = new MdnsResponseDecoder(mClock, null);
+        final ArraySet<MdnsResponse> updatedResponses = decode(
+                decoder, makeResponsePacket(DATAIN_IPV4_2), List.of(response));
+        assertEquals(1, updatedResponses.size());
+        assertEquals(parseNumericAddress("10.1.2.4"),
+                updatedResponses.valueAt(0).getInet4AddressRecord().getInet4Address());
+        assertEquals(parseNumericAddress("10.1.2.3"),
+                response.getInet4AddressRecord().getInet4Address());
+    }
+
+    @Test
+    public void testDecodeWithIpv6AddressChange() throws IOException {
+        MdnsResponse response = makeMdnsResponse(0, DATAIN_SERVICE_NAME_1, List.of(
+                new PacketAndRecordClass(DATAIN_PTR_1,
+                        MdnsPointerRecord.class),
+                new PacketAndRecordClass(DATAIN_SERVICE_1,
+                        MdnsServiceRecord.class),
+                new PacketAndRecordClass(DATAIN_IPV6_1,
+                        MdnsInet6AddressRecord.class)));
+        // Now update the response with another address
+        final MdnsResponseDecoder decoder = new MdnsResponseDecoder(mClock, null);
+        final ArraySet<MdnsResponse> updatedResponses = decode(
+                decoder, makeResponsePacket(DATAIN_IPV6_2), List.of(response));
+        assertEquals(1, updatedResponses.size());
+        assertEquals(parseNumericAddress("aabb:ccdd:1122:3344:a0b0:c0d0:1020:3030"),
+                updatedResponses.valueAt(0).getInet6AddressRecord().getInet6Address());
+        assertEquals(parseNumericAddress("aabb:ccdd:1122:3344:a0b0:c0d0:1020:3040"),
+                response.getInet6AddressRecord().getInet6Address());
+    }
+
+    @Test
+    public void testDecodeWithChangeOnText() throws IOException {
+        MdnsResponse response = makeMdnsResponse(0, DATAIN_SERVICE_NAME_1, List.of(
+                new PacketAndRecordClass(DATAIN_PTR_1,
+                        MdnsPointerRecord.class),
+                new PacketAndRecordClass(DATAIN_SERVICE_1,
+                        MdnsServiceRecord.class),
+                new PacketAndRecordClass(DATAIN_TEXT_1,
+                        MdnsTextRecord.class)));
+        // Now update the response with another address
+        final MdnsResponseDecoder decoder = new MdnsResponseDecoder(mClock, null);
+        final ArraySet<MdnsResponse> updatedResponses = decode(
+                decoder, makeResponsePacket(DATAIN_TEXT_2), List.of(response));
+        assertEquals(1, updatedResponses.size());
+        assertEquals(List.of(
+                new MdnsServiceInfo.TextEntry("a", "hello there"),
+                new MdnsServiceInfo.TextEntry("b", "1234567890"),
+                new MdnsServiceInfo.TextEntry("xyz", "!@#$")),
+                updatedResponses.valueAt(0).getTextRecord().getEntries());
+    }
+
+    @Test
+    public void testDecodeWithChangeOnService() throws IOException {
+        MdnsResponse response = makeMdnsResponse(0, DATAIN_SERVICE_NAME_1, List.of(
+                new PacketAndRecordClass(DATAIN_PTR_1,
+                        MdnsPointerRecord.class),
+                new PacketAndRecordClass(DATAIN_SERVICE_1,
+                        MdnsServiceRecord.class),
+                new PacketAndRecordClass(DATAIN_IPV4_1,
+                        MdnsInet4AddressRecord.class)));
+        assertArrayEquals(new String[] { "testhost1" },
+                response.getServiceRecord().getServiceHost());
+        assertNotNull(response.getInet4AddressRecord());
+        // Now update the response with another hostname
+        final MdnsResponseDecoder decoder = new MdnsResponseDecoder(mClock, null);
+        final ArraySet<MdnsResponse> updatedResponses = decode(
+                decoder, makeResponsePacket(DATAIN_SERVICE_2), List.of(response));
+        assertEquals(1, updatedResponses.size());
+        assertArrayEquals(new String[] { "testhost2" },
+                updatedResponses.valueAt(0).getServiceRecord().getServiceHost());
+        // Hostname changed, so address records are dropped
+        assertNull(updatedResponses.valueAt(0).getInet4AddressRecord());
+    }
+
+    @Test
+    public void testDecodeWithChangeOnPtr() throws IOException {
+        MdnsResponse response = makeMdnsResponse(0, DATAIN_SERVICE_NAME_1, List.of(
+                new PacketAndRecordClass(DATAIN_PTR_1,
+                        MdnsPointerRecord.class),
+                new PacketAndRecordClass(DATAIN_SERVICE_1,
+                        MdnsServiceRecord.class)));
+        // Now update the response with another address
+        final MdnsResponseDecoder decoder = new MdnsResponseDecoder(mClock, null);
+        final ArraySet<MdnsResponse> updatedResponses = decode(
+                decoder, makeResponsePacket(DATAIN_PTR_2), List.of(response));
+        assertEquals(1, updatedResponses.size());
+        assertArrayEquals(new String[] { "foo", "bar", "quxy" },
+                updatedResponses.valueAt(0).getPointerRecords().get(0).getPointer());
+    }
+
+    @Test
+    public void testDecodeWithNoChange() throws IOException {
+        List<PacketAndRecordClass> recordList =
+                Arrays.asList(
+                        new PacketAndRecordClass(DATAIN_IPV4_1, MdnsInet4AddressRecord.class),
+                        new PacketAndRecordClass(DATAIN_IPV6_1, MdnsInet6AddressRecord.class),
+                        new PacketAndRecordClass(DATAIN_PTR_1, MdnsPointerRecord.class),
+                        new PacketAndRecordClass(DATAIN_SERVICE_2, MdnsServiceRecord.class),
+                        new PacketAndRecordClass(DATAIN_TEXT_1, MdnsTextRecord.class));
+        // Create a two identical responses.
+        MdnsResponse response = makeMdnsResponse(0, DATAIN_SERVICE_NAME_1, recordList);
+
+        final MdnsResponseDecoder decoder = new MdnsResponseDecoder(mClock, null);
+        final byte[] identicalResponse = makeResponsePacket(
+                recordList.stream().map(p -> p.packetData).collect(Collectors.toList()));
+        final ArraySet<MdnsResponse> changes = decode(
+                decoder, identicalResponse, List.of(response));
+
+        // Decoding should not indicate any change.
+        assertEquals(0, changes.size());
+    }
+
+    private static MdnsResponse makeMdnsResponse(long time, String[] serviceName,
+            List<PacketAndRecordClass> responseList) throws IOException {
+        final MdnsResponse response = new MdnsResponse(
+                time, serviceName, 999 /* interfaceIndex */, mock(Network.class));
+        for (PacketAndRecordClass responseData : responseList) {
+            DatagramPacket packet =
+                    new DatagramPacket(responseData.packetData, responseData.packetData.length);
+            MdnsPacketReader reader = new MdnsPacketReader(packet);
+            String[] name = reader.readLabels();
+            reader.skip(2); // skip record type indication.
+            // Apply the right kind of record to the response.
+            if (responseData.recordClass == MdnsInet4AddressRecord.class) {
+                response.addInet4AddressRecord(new MdnsInet4AddressRecord(name, reader));
+            } else if (responseData.recordClass == MdnsInet6AddressRecord.class) {
+                response.addInet6AddressRecord(new MdnsInet6AddressRecord(name, reader));
+            } else if (responseData.recordClass == MdnsPointerRecord.class) {
+                response.addPointerRecord(new MdnsPointerRecord(name, reader));
+            } else if (responseData.recordClass == MdnsServiceRecord.class) {
+                response.setServiceRecord(new MdnsServiceRecord(name, reader));
+            } else if (responseData.recordClass == MdnsTextRecord.class) {
+                response.setTextRecord(new MdnsTextRecord(name, reader));
+            } else {
+                fail("Unsupported/unexpected MdnsRecord subtype used in test - invalid test!");
+            }
+        }
+        return response;
+    }
+
+    private static byte[] makeResponsePacket(byte[] responseRecord) throws IOException {
+        return makeResponsePacket(List.of(responseRecord));
+    }
+
+    private static byte[] makeResponsePacket(List<byte[]> responseRecords) throws IOException {
+        final MdnsPacketWriter writer = new MdnsPacketWriter(1500);
+        writer.writeUInt16(0); // Transaction ID (advertisement: 0)
+        writer.writeUInt16(0x8400); // Flags: response, authoritative
+        writer.writeUInt16(0); // questions count
+        writer.writeUInt16(responseRecords.size()); // answers count
+        writer.writeUInt16(0); // authority entries count
+        writer.writeUInt16(0); // additional records count
+
+        for (byte[] record : responseRecords) {
+            writer.writeBytes(record);
+        }
+        final DatagramPacket packet = writer.getPacket(new InetSocketAddress(0 /* port */));
+        return Arrays.copyOf(packet.getData(), packet.getLength());
+    }
+
+
+    // This helper class just wraps the data bytes of a response packet with the contained record
+    // type.
+    // Its only purpose is to make the test code a bit more readable.
+    private static class PacketAndRecordClass {
+        public final byte[] packetData;
+        public final Class<?> recordClass;
+
+        PacketAndRecordClass(byte[] data, Class<?> c) {
+            packetData = data;
+            recordClass = c;
+        }
+    }
+
+    private ArraySet<MdnsResponse> decode(MdnsResponseDecoder decoder, byte[] data)
+            throws MdnsPacket.ParseException {
+        return decode(decoder, data, Collections.emptyList());
+    }
+
+    private ArraySet<MdnsResponse> decode(MdnsResponseDecoder decoder, byte[] data,
+            Collection<MdnsResponse> existingResponses) throws MdnsPacket.ParseException {
+        final MdnsPacket parsedPacket = MdnsResponseDecoder.parseResponse(data, data.length);
+        assertNotNull(parsedPacket);
+
+        return decoder.augmentResponses(parsedPacket,
+                existingResponses,
+                MdnsSocket.INTERFACE_INDEX_UNSPECIFIED, mock(Network.class));
     }
 }
\ No newline at end of file
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsResponseTests.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsResponseTests.java
index ec57dc8..3f5e7a1 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsResponseTests.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsResponseTests.java
@@ -16,6 +16,8 @@
 
 package com.android.server.connectivity.mdns;
 
+import static android.net.InetAddresses.parseNumericAddress;
+
 import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
 
 import static org.junit.Assert.assertEquals;
@@ -23,22 +25,21 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
 import static org.mockito.Mockito.mock;
 
+import static java.util.Collections.emptyList;
+
 import android.net.Network;
 
 import com.android.net.module.util.HexDump;
 import com.android.testutils.DevSdkIgnoreRule;
 import com.android.testutils.DevSdkIgnoreRunner;
 
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 import java.io.IOException;
 import java.net.DatagramPacket;
-import java.util.Arrays;
 import java.util.List;
 
 // The record test data does not use compressed names (label pointers), since that would require
@@ -48,36 +49,24 @@
 public class MdnsResponseTests {
     private static final String TAG = "MdnsResponseTests";
     // MDNS response packet for name "test" with an IPv4 address of 10.1.2.3
-    private static final byte[] dataIn_ipv4_1 = HexDump.hexStringToByteArray(
+    private static final byte[] DATAIN_IPV4 = HexDump.hexStringToByteArray(
             "0474657374000001" + "0001000011940004" + "0A010203");
-    // MDNS response packet for name "tess" with an IPv4 address of 10.1.2.4
-    private static final byte[] dataIn_ipv4_2 = HexDump.hexStringToByteArray(
-            "0474657373000001" + "0001000011940004" + "0A010204");
     // MDNS response w/name "test" & IPv6 address of aabb:ccdd:1122:3344:a0b0:c0d0:1020:3040
-    private static final byte[] dataIn_ipv6_1 = HexDump.hexStringToByteArray(
+    private static final byte[] DATAIN_IPV6 = HexDump.hexStringToByteArray(
             "047465737400001C" + "0001000011940010" + "AABBCCDD11223344" + "A0B0C0D010203040");
-    // MDNS response w/name "test" & IPv6 address of aabb:ccdd:1122:3344:a0b0:c0d0:1020:3030
-    private static final byte[] dataIn_ipv6_2 = HexDump.hexStringToByteArray(
-            "047465737400001C" + "0001000011940010" + "AABBCCDD11223344" + "A0B0C0D010203030");
     // MDNS response w/name "test" & PTR to foo.bar.quxx
-    private static final byte[] dataIn_ptr_1 = HexDump.hexStringToByteArray(
+    private static final byte[] DATAIN_PTR = HexDump.hexStringToByteArray(
             "047465737400000C" + "000100001194000E" + "03666F6F03626172" + "047175787800");
-    // MDNS response w/name "test" & PTR to foo.bar.quxy
-    private static final byte[] dataIn_ptr_2 = HexDump.hexStringToByteArray(
-            "047465737400000C" + "000100001194000E" + "03666F6F03626172" + "047175787900");
     // MDNS response w/name "test" & Service for host foo.bar.quxx
-    private static final byte[] dataIn_service_1 = HexDump.hexStringToByteArray(
+    private static final byte[] DATAIN_SERVICE = HexDump.hexStringToByteArray(
             "0474657374000021"
             + "0001000011940014"
             + "000100FF1F480366"
             + "6F6F036261720471"
             + "75787800");
-    // MDNS response w/name "test" & Service for host test
-    private static final byte[] dataIn_service_2 = HexDump.hexStringToByteArray(
-            "0474657374000021" + "000100001194000B" + "000100FF1F480474" + "657374");
     // MDNS response w/name "test" & the following text strings:
     // "a=hello there", "b=1234567890", and "xyz=!$$$"
-    private static final byte[] dataIn_text_1 = HexDump.hexStringToByteArray(
+    private static final byte[] DATAIN_TEXT = HexDump.hexStringToByteArray(
             "0474657374000010"
             + "0001000011940024"
             + "0D613D68656C6C6F"
@@ -85,18 +74,11 @@
             + "3D31323334353637"
             + "3839300878797A3D"
             + "21242424");
-    // MDNS response w/name "test" & the following text strings:
-    // "a=hello there", "b=1234567890", and "xyz=!@#$"
-    private static final byte[] dataIn_text_2 = HexDump.hexStringToByteArray(
-            "0474657374000010"
-            + "0001000011940024"
-            + "0D613D68656C6C6F"
-            + "2074686572650C62"
-            + "3D31323334353637"
-            + "3839300878797A3D"
-            + "21402324");
+    private static final String[] TEST_SERVICE_NAME =
+            new String[] { "test", "_type", "_tcp", "local" };
 
     private static final int INTERFACE_INDEX = 999;
+    private static final int TEST_TTL_MS = 120_000;
     private final Network mNetwork = mock(Network.class);
 
     // The following helper classes act as wrappers so that IPv4 and IPv6 address records can
@@ -113,87 +95,63 @@
         }
     }
 
-    // This helper class just wraps the data bytes of a response packet with the contained record
-    // type.
-    // Its only purpose is to make the test code a bit more readable.
-    static class PacketAndRecordClass {
-        public final byte[] packetData;
-        public final Class<?> recordClass;
-
-        public PacketAndRecordClass() {
-            packetData = null;
-            recordClass = null;
-        }
-
-        public PacketAndRecordClass(byte[] data, Class<?> c) {
-            packetData = data;
-            recordClass = c;
-        }
-    }
-
-    // Construct an MdnsResponse with the specified data packets applied.
-    private MdnsResponse makeMdnsResponse(long time, List<PacketAndRecordClass> responseList)
-            throws IOException {
-        MdnsResponse response = new MdnsResponse(time, INTERFACE_INDEX, mNetwork);
-        for (PacketAndRecordClass responseData : responseList) {
-            DatagramPacket packet =
-                    new DatagramPacket(responseData.packetData, responseData.packetData.length);
-            MdnsPacketReader reader = new MdnsPacketReader(packet);
-            String[] name = reader.readLabels();
-            reader.skip(2); // skip record type indication.
-            // Apply the right kind of record to the response.
-            if (responseData.recordClass == MdnsInet4AddressRecord.class) {
-                response.setInet4AddressRecord(new MdnsInet4AddressRecord(name, reader));
-            } else if (responseData.recordClass == MdnsInet6AddressRecord.class) {
-                response.setInet6AddressRecord(new MdnsInet6AddressRecord(name, reader));
-            } else if (responseData.recordClass == MdnsPointerRecord.class) {
-                response.addPointerRecord(new MdnsPointerRecord(name, reader));
-            } else if (responseData.recordClass == MdnsServiceRecord.class) {
-                response.setServiceRecord(new MdnsServiceRecord(name, reader));
-            } else if (responseData.recordClass == MdnsTextRecord.class) {
-                response.setTextRecord(new MdnsTextRecord(name, reader));
-            } else {
-                fail("Unsupported/unexpected MdnsRecord subtype used in test - invalid test!");
-            }
-        }
+    private MdnsResponse makeCompleteResponse(int recordsTtlMillis) {
+        final String[] hostname = new String[] { "MyHostname" };
+        final String[] serviceName = new String[] { "MyService", "_type", "_tcp", "local" };
+        final String[] serviceType = new String[] { "_type", "_tcp", "local" };
+        final MdnsResponse response = new MdnsResponse(/* now= */ 0, serviceName, INTERFACE_INDEX,
+                mNetwork);
+        response.addPointerRecord(new MdnsPointerRecord(serviceType, 0L /* receiptTimeMillis */,
+                false /* cacheFlush */, recordsTtlMillis, serviceName));
+        response.setServiceRecord(new MdnsServiceRecord(serviceName, 0L /* receiptTimeMillis */,
+                true /* cacheFlush */, recordsTtlMillis, 0 /* servicePriority */,
+                0 /* serviceWeight */, 0 /* servicePort */, hostname));
+        response.setTextRecord(new MdnsTextRecord(serviceName, 0L /* receiptTimeMillis */,
+                true /* cacheFlush */, recordsTtlMillis, emptyList() /* entries */));
+        response.addInet4AddressRecord(new MdnsInetAddressRecord(
+                hostname, 0L /* receiptTimeMillis */, true /* cacheFlush */,
+                recordsTtlMillis, parseNumericAddress("192.0.2.123")));
+        response.addInet6AddressRecord(new MdnsInetAddressRecord(
+                hostname, 0L /* receiptTimeMillis */, true /* cacheFlush */,
+                recordsTtlMillis, parseNumericAddress("2001:db8::123")));
         return response;
     }
 
     @Test
     public void getInet4AddressRecord_returnsAddedRecord() throws IOException {
-        DatagramPacket packet = new DatagramPacket(dataIn_ipv4_1, dataIn_ipv4_1.length);
+        DatagramPacket packet = new DatagramPacket(DATAIN_IPV4, DATAIN_IPV4.length);
         MdnsPacketReader reader = new MdnsPacketReader(packet);
         String[] name = reader.readLabels();
         reader.skip(2); // skip record type indication.
         MdnsInetAddressRecord record = new MdnsInetAddressRecord(name, MdnsRecord.TYPE_A, reader);
-        MdnsResponse response = new MdnsResponse(0, INTERFACE_INDEX, mNetwork);
+        MdnsResponse response = new MdnsResponse(0, TEST_SERVICE_NAME, INTERFACE_INDEX, mNetwork);
         assertFalse(response.hasInet4AddressRecord());
-        assertTrue(response.setInet4AddressRecord(record));
+        assertTrue(response.addInet4AddressRecord(record));
         assertEquals(response.getInet4AddressRecord(), record);
     }
 
     @Test
     public void getInet6AddressRecord_returnsAddedRecord() throws IOException {
-        DatagramPacket packet = new DatagramPacket(dataIn_ipv6_1, dataIn_ipv6_1.length);
+        DatagramPacket packet = new DatagramPacket(DATAIN_IPV6, DATAIN_IPV6.length);
         MdnsPacketReader reader = new MdnsPacketReader(packet);
         String[] name = reader.readLabels();
         reader.skip(2); // skip record type indication.
         MdnsInetAddressRecord record =
                 new MdnsInetAddressRecord(name, MdnsRecord.TYPE_AAAA, reader);
-        MdnsResponse response = new MdnsResponse(0, INTERFACE_INDEX, mNetwork);
+        MdnsResponse response = new MdnsResponse(0, TEST_SERVICE_NAME, INTERFACE_INDEX, mNetwork);
         assertFalse(response.hasInet6AddressRecord());
-        assertTrue(response.setInet6AddressRecord(record));
+        assertTrue(response.addInet6AddressRecord(record));
         assertEquals(response.getInet6AddressRecord(), record);
     }
 
     @Test
     public void getPointerRecords_returnsAddedRecord() throws IOException {
-        DatagramPacket packet = new DatagramPacket(dataIn_ptr_1, dataIn_ptr_1.length);
+        DatagramPacket packet = new DatagramPacket(DATAIN_PTR, DATAIN_PTR.length);
         MdnsPacketReader reader = new MdnsPacketReader(packet);
         String[] name = reader.readLabels();
         reader.skip(2); // skip record type indication.
         MdnsPointerRecord record = new MdnsPointerRecord(name, reader);
-        MdnsResponse response = new MdnsResponse(0, INTERFACE_INDEX, mNetwork);
+        MdnsResponse response = new MdnsResponse(0, record.getPointer(), INTERFACE_INDEX, mNetwork);
         assertFalse(response.hasPointerRecords());
         assertTrue(response.addPointerRecord(record));
         List<MdnsPointerRecord> recordList = response.getPointerRecords();
@@ -204,12 +162,12 @@
 
     @Test
     public void getServiceRecord_returnsAddedRecord() throws IOException {
-        DatagramPacket packet = new DatagramPacket(dataIn_service_1, dataIn_service_1.length);
+        DatagramPacket packet = new DatagramPacket(DATAIN_SERVICE, DATAIN_SERVICE.length);
         MdnsPacketReader reader = new MdnsPacketReader(packet);
         String[] name = reader.readLabels();
         reader.skip(2); // skip record type indication.
         MdnsServiceRecord record = new MdnsServiceRecord(name, reader);
-        MdnsResponse response = new MdnsResponse(0, INTERFACE_INDEX, mNetwork);
+        MdnsResponse response = new MdnsResponse(0, name, INTERFACE_INDEX, mNetwork);
         assertFalse(response.hasServiceRecord());
         assertTrue(response.setServiceRecord(record));
         assertEquals(response.getServiceRecord(), record);
@@ -217,12 +175,12 @@
 
     @Test
     public void getTextRecord_returnsAddedRecord() throws IOException {
-        DatagramPacket packet = new DatagramPacket(dataIn_text_1, dataIn_text_1.length);
+        DatagramPacket packet = new DatagramPacket(DATAIN_TEXT, DATAIN_TEXT.length);
         MdnsPacketReader reader = new MdnsPacketReader(packet);
         String[] name = reader.readLabels();
         reader.skip(2); // skip record type indication.
         MdnsTextRecord record = new MdnsTextRecord(name, reader);
-        MdnsResponse response = new MdnsResponse(0, INTERFACE_INDEX, mNetwork);
+        MdnsResponse response = new MdnsResponse(0, name, INTERFACE_INDEX, mNetwork);
         assertFalse(response.hasTextRecord());
         assertTrue(response.setTextRecord(record));
         assertEquals(response.getTextRecord(), record);
@@ -230,104 +188,86 @@
 
     @Test
     public void getInterfaceIndex() {
-        final MdnsResponse response1 = new MdnsResponse(/* now= */ 0, INTERFACE_INDEX, mNetwork);
+        final MdnsResponse response1 = new MdnsResponse(/* now= */ 0, TEST_SERVICE_NAME,
+                INTERFACE_INDEX, mNetwork);
         assertEquals(INTERFACE_INDEX, response1.getInterfaceIndex());
 
-        final MdnsResponse response2 =
-                new MdnsResponse(/* now= */ 0, 1234 /* interfaceIndex */, mNetwork);
+        final MdnsResponse response2 = new MdnsResponse(/* now= */ 0, TEST_SERVICE_NAME,
+                1234 /* interfaceIndex */, mNetwork);
         assertEquals(1234, response2.getInterfaceIndex());
     }
 
     @Test
     public void testGetNetwork() {
-        final MdnsResponse response1 =
-                new MdnsResponse(/* now= */ 0, INTERFACE_INDEX, null /* network */);
+        final MdnsResponse response1 = new MdnsResponse(/* now= */ 0, TEST_SERVICE_NAME,
+                INTERFACE_INDEX, null /* network */);
         assertNull(response1.getNetwork());
 
-        final MdnsResponse response2 =
-                new MdnsResponse(/* now= */ 0, 1234 /* interfaceIndex */, mNetwork);
+        final MdnsResponse response2 = new MdnsResponse(/* now= */ 0, TEST_SERVICE_NAME,
+                1234 /* interfaceIndex */, mNetwork);
         assertEquals(mNetwork, response2.getNetwork());
     }
 
     @Test
-    public void mergeRecordsFrom_indicates_change_on_ipv4_address() throws IOException {
-        MdnsResponse response = makeMdnsResponse(
-                0,
-                Arrays.asList(
-                        new PacketAndRecordClass(dataIn_ipv4_1, MdnsInet4AddressRecord.class)));
-        // Now create a new response that updates the address.
-        MdnsResponse response2 = makeMdnsResponse(
-                100,
-                Arrays.asList(
-                        new PacketAndRecordClass(dataIn_ipv4_2, MdnsInet4AddressRecord.class)));
-        assertTrue(response.mergeRecordsFrom(response2));
+    public void copyConstructor() {
+        final MdnsResponse response = makeCompleteResponse(TEST_TTL_MS);
+        final MdnsResponse copy = new MdnsResponse(response);
+
+        assertEquals(response.getInet6AddressRecord(), copy.getInet6AddressRecord());
+        assertEquals(response.getInet4AddressRecord(), copy.getInet4AddressRecord());
+        assertEquals(response.getPointerRecords(), copy.getPointerRecords());
+        assertEquals(response.getServiceRecord(), copy.getServiceRecord());
+        assertEquals(response.getTextRecord(), copy.getTextRecord());
+        assertEquals(response.getRecords(), copy.getRecords());
+        assertEquals(response.getNetwork(), copy.getNetwork());
+        assertEquals(response.getInterfaceIndex(), copy.getInterfaceIndex());
     }
 
     @Test
-    public void mergeRecordsFrom_indicates_change_on_ipv6_address() throws IOException {
-        MdnsResponse response = makeMdnsResponse(
-                0,
-                Arrays.asList(
-                        new PacketAndRecordClass(dataIn_ipv6_1, MdnsInet6AddressRecord.class)));
-        // Now create a new response that updates the address.
-        MdnsResponse response2 = makeMdnsResponse(
-                100,
-                Arrays.asList(
-                        new PacketAndRecordClass(dataIn_ipv6_2, MdnsInet6AddressRecord.class)));
-        assertTrue(response.mergeRecordsFrom(response2));
+    public void addRecords_noChange() {
+        final MdnsResponse response = makeCompleteResponse(TEST_TTL_MS);
+
+        assertFalse(response.addPointerRecord(response.getPointerRecords().get(0)));
+        assertFalse(response.addInet6AddressRecord(response.getInet6AddressRecord()));
+        assertFalse(response.addInet4AddressRecord(response.getInet4AddressRecord()));
+        assertFalse(response.setServiceRecord(response.getServiceRecord()));
+        assertFalse(response.setTextRecord(response.getTextRecord()));
     }
 
     @Test
-    public void mergeRecordsFrom_indicates_change_on_text() throws IOException {
-        MdnsResponse response = makeMdnsResponse(
-                0,
-                Arrays.asList(new PacketAndRecordClass(dataIn_text_1, MdnsTextRecord.class)));
-        // Now create a new response that updates the address.
-        MdnsResponse response2 = makeMdnsResponse(
-                100,
-                Arrays.asList(new PacketAndRecordClass(dataIn_text_2, MdnsTextRecord.class)));
-        assertTrue(response.mergeRecordsFrom(response2));
-    }
+    public void addRecords_ttlChange() {
+        final MdnsResponse response = makeCompleteResponse(TEST_TTL_MS);
+        final MdnsResponse ttlZeroResponse = makeCompleteResponse(0);
 
-    @Test
-    public void mergeRecordsFrom_indicates_change_on_service() throws IOException {
-        MdnsResponse response = makeMdnsResponse(
-                0,
-                Arrays.asList(new PacketAndRecordClass(dataIn_service_1, MdnsServiceRecord.class)));
-        // Now create a new response that updates the address.
-        MdnsResponse response2 = makeMdnsResponse(
-                100,
-                Arrays.asList(new PacketAndRecordClass(dataIn_service_2, MdnsServiceRecord.class)));
-        assertTrue(response.mergeRecordsFrom(response2));
-    }
+        assertTrue(response.addPointerRecord(ttlZeroResponse.getPointerRecords().get(0)));
+        assertEquals(1, response.getPointerRecords().size());
+        assertEquals(0, response.getPointerRecords().get(0).getTtl());
+        assertTrue(response.getRecords().stream().anyMatch(r ->
+                r == response.getPointerRecords().get(0)));
 
-    @Test
-    public void mergeRecordsFrom_indicates_change_on_pointer() throws IOException {
-        MdnsResponse response = makeMdnsResponse(
-                0,
-                Arrays.asList(new PacketAndRecordClass(dataIn_ptr_1, MdnsPointerRecord.class)));
-        // Now create a new response that updates the address.
-        MdnsResponse response2 = makeMdnsResponse(
-                100,
-                Arrays.asList(new PacketAndRecordClass(dataIn_ptr_2, MdnsPointerRecord.class)));
-        assertTrue(response.mergeRecordsFrom(response2));
-    }
+        assertTrue(response.addInet6AddressRecord(ttlZeroResponse.getInet6AddressRecord()));
+        assertEquals(1, response.getInet6AddressRecords().size());
+        assertEquals(0, response.getInet6AddressRecord().getTtl());
+        assertTrue(response.getRecords().stream().anyMatch(r ->
+                r == response.getInet6AddressRecord()));
 
-    @Test
-    @Ignore("MdnsConfigs is not configurable currently.")
-    public void mergeRecordsFrom_indicates_noChange() throws IOException {
-        //MdnsConfigsFlagsImpl.useReducedMergeRecordUpdateEvents.override(true);
-        List<PacketAndRecordClass> recordList =
-                Arrays.asList(
-                        new PacketAndRecordClass(dataIn_ipv4_1, MdnsInet4AddressRecord.class),
-                        new PacketAndRecordClass(dataIn_ipv6_1, MdnsInet6AddressRecord.class),
-                        new PacketAndRecordClass(dataIn_ptr_1, MdnsPointerRecord.class),
-                        new PacketAndRecordClass(dataIn_service_2, MdnsServiceRecord.class),
-                        new PacketAndRecordClass(dataIn_text_1, MdnsTextRecord.class));
-        // Create a two identical responses.
-        MdnsResponse response = makeMdnsResponse(0, recordList);
-        MdnsResponse response2 = makeMdnsResponse(100, recordList);
-        // Merging should not indicate any change.
-        assertFalse(response.mergeRecordsFrom(response2));
+        assertTrue(response.addInet4AddressRecord(ttlZeroResponse.getInet4AddressRecord()));
+        assertEquals(1, response.getInet4AddressRecords().size());
+        assertEquals(0, response.getInet4AddressRecord().getTtl());
+        assertTrue(response.getRecords().stream().anyMatch(r ->
+                r == response.getInet4AddressRecord()));
+
+        assertTrue(response.setServiceRecord(ttlZeroResponse.getServiceRecord()));
+        assertEquals(0, response.getServiceRecord().getTtl());
+        assertTrue(response.getRecords().stream().anyMatch(r ->
+                r == response.getServiceRecord()));
+
+        assertTrue(response.setTextRecord(ttlZeroResponse.getTextRecord()));
+        assertEquals(0, response.getTextRecord().getTtl());
+        assertTrue(response.getRecords().stream().anyMatch(r ->
+                r == response.getTextRecord()));
+
+        // All records were replaced, not added
+        assertEquals(ttlZeroResponse.getRecords().size(), response.getRecords().size());
     }
 }
\ No newline at end of file
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceInfoTest.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceInfoTest.java
index 76728cf..e7d7a98 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceInfoTest.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceInfoTest.java
@@ -119,6 +119,26 @@
     }
 
     @Test
+    public void constructor_createWithUppercaseKeys_correctAttributes() {
+        MdnsServiceInfo info =
+                new MdnsServiceInfo(
+                        "my-mdns-service",
+                        new String[] {"_testtype", "_tcp"},
+                        List.of(),
+                        new String[] {"my-host", "local"},
+                        12345,
+                        "192.168.1.1",
+                        "2001::1",
+                        List.of("KEY=Value"),
+                        /* textEntries= */ null);
+
+        assertEquals("Value", info.getAttributeByKey("key"));
+        assertEquals("Value", info.getAttributeByKey("KEY"));
+        assertEquals(1, info.getAttributes().size());
+        assertEquals("KEY", info.getAttributes().keySet().iterator().next());
+    }
+
+    @Test
     public void getInterfaceIndex_constructorWithDefaultValues_returnsMinusOne() {
         MdnsServiceInfo info =
                 new MdnsServiceInfo(
@@ -177,8 +197,8 @@
                         List.of(),
                         new String[] {"my-host", "local"},
                         12345,
-                        "192.168.1.1",
-                        "2001::1",
+                        List.of("192.168.1.1"),
+                        List.of("2001::1"),
                         List.of(),
                         /* textEntries= */ null,
                         /* interfaceIndex= */ 20,
@@ -197,8 +217,8 @@
                         List.of(),
                         new String[] {"my-host", "local"},
                         12345,
-                        "192.168.1.1",
-                        "2001::1",
+                        List.of("192.168.1.1", "192.168.1.2"),
+                        List.of("2001::1", "2001::2"),
                         List.of("vn=Alphabet Inc.", "mn=Google Nest Hub Max", "id=12345"),
                         List.of(
                                 MdnsServiceInfo.TextEntry.fromString("vn=Google Inc."),
@@ -217,7 +237,9 @@
         assertArrayEquals(beforeParcel.getHostName(), afterParcel.getHostName());
         assertEquals(beforeParcel.getPort(), afterParcel.getPort());
         assertEquals(beforeParcel.getIpv4Address(), afterParcel.getIpv4Address());
+        assertEquals(beforeParcel.getIpv4Addresses(), afterParcel.getIpv4Addresses());
         assertEquals(beforeParcel.getIpv6Address(), afterParcel.getIpv6Address());
+        assertEquals(beforeParcel.getIpv6Addresses(), afterParcel.getIpv6Addresses());
         assertEquals(beforeParcel.getAttributes(), afterParcel.getAttributes());
         assertEquals(beforeParcel.getInterfaceIndex(), afterParcel.getInterfaceIndex());
         assertEquals(beforeParcel.getNetwork(), afterParcel.getNetwork());
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 a45ca68..3e2ea35 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java
@@ -24,13 +24,12 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.inOrder;
-import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -60,8 +59,7 @@
 
 import java.io.IOException;
 import java.net.DatagramPacket;
-import java.net.Inet4Address;
-import java.net.Inet6Address;
+import java.net.InetAddress;
 import java.net.InetSocketAddress;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -72,6 +70,7 @@
 import java.util.concurrent.ScheduledFuture;
 import java.util.concurrent.ScheduledThreadPoolExecutor;
 import java.util.concurrent.TimeUnit;
+import java.util.stream.Stream;
 
 /** Tests for {@link MdnsServiceTypeClient}. */
 @RunWith(DevSdkIgnoreRunner.class)
@@ -85,6 +84,9 @@
     private static final InetSocketAddress IPV6_ADDRESS = new InetSocketAddress(
             MdnsConstants.getMdnsIPv6Address(), MdnsConstants.MDNS_PORT);
 
+    private static final long TEST_TTL = 120000L;
+    private static final long TEST_ELAPSED_REALTIME = 123L;
+
     @Mock
     private MdnsServiceBrowserListener mockListenerOne;
     @Mock
@@ -95,6 +97,8 @@
     private MdnsMultinetworkSocketClient mockSocketClient;
     @Mock
     private Network mockNetwork;
+    @Mock
+    private MdnsResponseDecoder.Clock mockDecoderClock;
     @Captor
     private ArgumentCaptor<MdnsServiceInfo> serviceInfoCaptor;
 
@@ -111,6 +115,7 @@
     @SuppressWarnings("DoNotMock")
     public void setUp() throws IOException {
         MockitoAnnotations.initMocks(this);
+        doReturn(TEST_ELAPSED_REALTIME).when(mockDecoderClock).elapsedRealtime();
 
         expectedIPv4Packets = new DatagramPacket[16];
         expectedIPv6Packets = new DatagramPacket[16];
@@ -160,7 +165,8 @@
                 .thenReturn(expectedIPv6Packets[15]);
 
         client =
-                new MdnsServiceTypeClient(SERVICE_TYPE, mockSocketClient, currentThreadExecutor) {
+                new MdnsServiceTypeClient(SERVICE_TYPE, mockSocketClient, currentThreadExecutor,
+                        mockDecoderClock, mockNetwork) {
                     @Override
                     MdnsPacketWriter createMdnsPacketWriter() {
                         return mockPacketWriter;
@@ -414,16 +420,17 @@
     }
 
     private static void verifyServiceInfo(MdnsServiceInfo serviceInfo, String serviceName,
-            String[] serviceType, String ipv4Address, String ipv6Address, int port,
+            String[] serviceType, List<String> ipv4Addresses, List<String> ipv6Addresses, int port,
             List<String> subTypes, Map<String, String> attributes, int interfaceIndex,
             Network network) {
         assertEquals(serviceName, serviceInfo.getServiceInstanceName());
         assertArrayEquals(serviceType, serviceInfo.getServiceType());
-        assertEquals(ipv4Address, serviceInfo.getIpv4Address());
-        assertEquals(ipv6Address, serviceInfo.getIpv6Address());
+        assertEquals(ipv4Addresses, serviceInfo.getIpv4Addresses());
+        assertEquals(ipv6Addresses, serviceInfo.getIpv6Addresses());
         assertEquals(port, serviceInfo.getPort());
         assertEquals(subTypes, serviceInfo.getSubtypes());
         for (String key : attributes.keySet()) {
+            assertTrue(attributes.containsKey(key));
             assertEquals(attributes.get(key), serviceInfo.getAttributeByKey(key));
         }
         assertEquals(interfaceIndex, serviceInfo.getInterfaceIndex());
@@ -434,22 +441,19 @@
     public void processResponse_incompleteResponse() {
         client.startSendAndReceive(mockListenerOne, MdnsSearchOptions.getDefaultOptions());
 
-        MdnsResponse response = mock(MdnsResponse.class);
-        when(response.getServiceInstanceName()).thenReturn("service-instance-1");
-        doReturn(INTERFACE_INDEX).when(response).getInterfaceIndex();
-        doReturn(mockNetwork).when(response).getNetwork();
-        when(response.isComplete()).thenReturn(false);
-
-        client.processResponse(response);
+        client.processResponse(createResponse(
+                "service-instance-1", null /* host */, 0 /* port */,
+                SERVICE_TYPE_LABELS,
+                Collections.emptyMap(), TEST_TTL), INTERFACE_INDEX, mockNetwork);
         verify(mockListenerOne).onServiceNameDiscovered(serviceInfoCaptor.capture());
         verifyServiceInfo(serviceInfoCaptor.getAllValues().get(0),
                 "service-instance-1",
                 SERVICE_TYPE_LABELS,
-                null /* ipv4Address */,
-                null /* ipv6Address */,
-                0 /* port */,
-                List.of() /* subTypes */,
-                Collections.singletonMap("key", null) /* attributes */,
+                /* ipv4Address= */ List.of(),
+                /* ipv6Address= */ List.of(),
+                /* port= */ 0,
+                /* subTypes= */ List.of(),
+                Collections.emptyMap(),
                 INTERFACE_INDEX,
                 mockNetwork);
 
@@ -463,36 +467,25 @@
         client.startSendAndReceive(mockListenerOne, MdnsSearchOptions.getDefaultOptions());
 
         // Process the initial response.
-        MdnsResponse initialResponse =
-                createResponse(
-                        "service-instance-1",
-                        ipV4Address,
-                        5353,
-                        /* subtype= */ "ABCDE",
-                        Collections.emptyMap(),
-                        /* interfaceIndex= */ 20,
-                        mockNetwork);
-        client.processResponse(initialResponse);
+        client.processResponse(createResponse(
+                "service-instance-1", ipV4Address, 5353,
+                /* subtype= */ "ABCDE",
+                Collections.emptyMap(), TEST_TTL), /* interfaceIndex= */ 20, mockNetwork);
 
         // Process a second response with a different port and updated text attributes.
-        MdnsResponse secondResponse =
-                createResponse(
-                        "service-instance-1",
-                        ipV4Address,
-                        5354,
-                        /* subtype= */ "ABCDE",
-                        Collections.singletonMap("key", "value"),
-                        /* interfaceIndex= */ 20,
-                        mockNetwork);
-        client.processResponse(secondResponse);
+        client.processResponse(createResponse(
+                "service-instance-1", ipV4Address, 5354,
+                /* subtype= */ "ABCDE",
+                Collections.singletonMap("key", "value"), TEST_TTL),
+                /* interfaceIndex= */ 20, mockNetwork);
 
         // Verify onServiceNameDiscovered was called once for the initial response.
         verify(mockListenerOne).onServiceNameDiscovered(serviceInfoCaptor.capture());
         verifyServiceInfo(serviceInfoCaptor.getAllValues().get(0),
                 "service-instance-1",
                 SERVICE_TYPE_LABELS,
-                ipV4Address /* ipv4Address */,
-                null /* ipv6Address */,
+                List.of(ipV4Address) /* ipv4Address */,
+                List.of() /* ipv6Address */,
                 5353 /* port */,
                 Collections.singletonList("ABCDE") /* subTypes */,
                 Collections.singletonMap("key", null) /* attributes */,
@@ -529,39 +522,25 @@
         client.startSendAndReceive(mockListenerOne, MdnsSearchOptions.getDefaultOptions());
 
         // Process the initial response.
-        MdnsResponse initialResponse =
-                createResponse(
-                        "service-instance-1",
-                        ipV6Address,
-                        5353,
-                        /* subtype= */ "ABCDE",
-                        Collections.emptyMap(),
-                        /* interfaceIndex= */ 20,
-                        mockNetwork);
-        client.processResponse(initialResponse);
+        client.processResponse(createResponse(
+                "service-instance-1", ipV6Address, 5353,
+                /* subtype= */ "ABCDE",
+                Collections.emptyMap(), TEST_TTL), /* interfaceIndex= */ 20, mockNetwork);
 
         // Process a second response with a different port and updated text attributes.
-        MdnsResponse secondResponse =
-                createResponse(
-                        "service-instance-1",
-                        ipV6Address,
-                        5354,
-                        /* subtype= */ "ABCDE",
-                        Collections.singletonMap("key", "value"),
-                        /* interfaceIndex= */ 20,
-                        mockNetwork);
-        client.processResponse(secondResponse);
-
-        System.out.println("secondResponses ip"
-                + secondResponse.getInet6AddressRecord().getInet6Address().getHostAddress());
+        client.processResponse(createResponse(
+                "service-instance-1", ipV6Address, 5354,
+                /* subtype= */ "ABCDE",
+                Collections.singletonMap("key", "value"), TEST_TTL),
+                /* interfaceIndex= */ 20, mockNetwork);
 
         // Verify onServiceNameDiscovered was called once for the initial response.
         verify(mockListenerOne).onServiceNameDiscovered(serviceInfoCaptor.capture());
         verifyServiceInfo(serviceInfoCaptor.getAllValues().get(0),
                 "service-instance-1",
                 SERVICE_TYPE_LABELS,
-                null /* ipv4Address */,
-                ipV6Address /* ipv6Address */,
+                List.of() /* ipv4Address */,
+                List.of(ipV6Address) /* ipv6Address */,
                 5353 /* port */,
                 Collections.singletonList("ABCDE") /* subTypes */,
                 Collections.singletonMap("key", null) /* attributes */,
@@ -619,29 +598,25 @@
         final String serviceName = "service-instance-1";
         final String ipV6Address = "2000:3333::da6c:63ff:fe7c:7483";
         // Process the initial response.
-        final MdnsResponse initialResponse =
-                createResponse(
-                        serviceName,
-                        ipV6Address,
-                        5353 /* port */,
-                        /* subtype= */ "ABCDE",
-                        Collections.emptyMap(),
-                        INTERFACE_INDEX,
-                        mockNetwork);
-        client.processResponse(initialResponse);
-        MdnsResponse response = mock(MdnsResponse.class);
-        doReturn("goodbye-service").when(response).getServiceInstanceName();
-        doReturn(INTERFACE_INDEX).when(response).getInterfaceIndex();
-        doReturn(mockNetwork).when(response).getNetwork();
-        doReturn(true).when(response).isGoodbye();
-        client.processResponse(response);
+        client.processResponse(createResponse(
+                serviceName, ipV6Address, 5353,
+                SERVICE_TYPE_LABELS,
+                Collections.emptyMap(), TEST_TTL), INTERFACE_INDEX, mockNetwork);
+
+        client.processResponse(createResponse(
+                "goodbye-service", ipV6Address, 5353,
+                SERVICE_TYPE_LABELS,
+                Collections.emptyMap(), /* ptrTtlMillis= */ 0L), INTERFACE_INDEX, mockNetwork);
+
         // Verify removed callback won't be called if the service is not existed.
         verifyServiceRemovedNoCallback(mockListenerOne);
         verifyServiceRemovedNoCallback(mockListenerTwo);
 
         // Verify removed callback would be called.
-        doReturn(serviceName).when(response).getServiceInstanceName();
-        client.processResponse(response);
+        client.processResponse(createResponse(
+                serviceName, ipV6Address, 5353,
+                SERVICE_TYPE_LABELS,
+                Collections.emptyMap(), 0L), INTERFACE_INDEX, mockNetwork);
         verifyServiceRemovedCallback(
                 mockListenerOne, serviceName, SERVICE_TYPE_LABELS, INTERFACE_INDEX, mockNetwork);
         verifyServiceRemovedCallback(
@@ -651,16 +626,10 @@
     @Test
     public void reportExistingServiceToNewlyRegisteredListeners() throws Exception {
         // Process the initial response.
-        MdnsResponse initialResponse =
-                createResponse(
-                        "service-instance-1",
-                        "192.168.1.1",
-                        5353,
-                        /* subtype= */ "ABCDE",
-                        Collections.emptyMap(),
-                        INTERFACE_INDEX,
-                        mockNetwork);
-        client.processResponse(initialResponse);
+        client.processResponse(createResponse(
+                "service-instance-1", "192.168.1.1", 5353,
+                /* subtype= */ "ABCDE",
+                Collections.emptyMap(), TEST_TTL), INTERFACE_INDEX, mockNetwork);
 
         client.startSendAndReceive(mockListenerOne, MdnsSearchOptions.getDefaultOptions());
 
@@ -669,8 +638,8 @@
         verifyServiceInfo(serviceInfoCaptor.getAllValues().get(0),
                 "service-instance-1",
                 SERVICE_TYPE_LABELS,
-                "192.168.1.1" /* ipv4Address */,
-                null /* ipv6Address */,
+                List.of("192.168.1.1") /* ipv4Address */,
+                List.of() /* ipv6Address */,
                 5353 /* port */,
                 Collections.singletonList("ABCDE") /* subTypes */,
                 Collections.singletonMap("key", null) /* attributes */,
@@ -687,10 +656,10 @@
         assertNull(existingServiceInfo.getAttributeByKey("key"));
 
         // Process a goodbye message for the existing response.
-        MdnsResponse goodByeResponse = mock(MdnsResponse.class);
-        when(goodByeResponse.getServiceInstanceName()).thenReturn("service-instance-1");
-        when(goodByeResponse.isGoodbye()).thenReturn(true);
-        client.processResponse(goodByeResponse);
+        client.processResponse(createResponse(
+                "service-instance-1", "192.168.1.1", 5353,
+                SERVICE_TYPE_LABELS,
+                Collections.emptyMap(), /* ptrTtlMillis= */ 0L), INTERFACE_INDEX, mockNetwork);
 
         client.startSendAndReceive(mockListenerTwo, MdnsSearchOptions.getDefaultOptions());
 
@@ -709,17 +678,15 @@
         Runnable firstMdnsTask = currentThreadExecutor.getAndClearSubmittedRunnable();
 
         // Process the initial response.
-        MdnsResponse initialResponse =
-                createMockResponse(
-                        serviceInstanceName, "192.168.1.1", 5353, List.of("ABCDE"),
-                        Map.of(), INTERFACE_INDEX, mockNetwork);
-        client.processResponse(initialResponse);
+        client.processResponse(createResponse(
+                serviceInstanceName, "192.168.1.1", 5353, /* subtype= */ "ABCDE",
+                Collections.emptyMap(), TEST_TTL), INTERFACE_INDEX, mockNetwork);
 
         // Clear the scheduled runnable.
         currentThreadExecutor.getAndClearLastScheduledRunnable();
 
         // Simulate the case where the response is after TTL.
-        when(initialResponse.getServiceRecord().getRemainingTTL(anyLong())).thenReturn((long) 0);
+        doReturn(TEST_ELAPSED_REALTIME + TEST_TTL + 1L).when(mockDecoderClock).elapsedRealtime();
         firstMdnsTask.run();
 
         // Verify removed callback was not called.
@@ -733,7 +700,8 @@
         //MdnsConfigsFlagsImpl.allowSearchOptionsToRemoveExpiredService.override(true);
         final String serviceInstanceName = "service-instance-1";
         client =
-                new MdnsServiceTypeClient(SERVICE_TYPE, mockSocketClient, currentThreadExecutor) {
+                new MdnsServiceTypeClient(SERVICE_TYPE, mockSocketClient, currentThreadExecutor,
+                        mockDecoderClock, mockNetwork) {
                     @Override
                     MdnsPacketWriter createMdnsPacketWriter() {
                         return mockPacketWriter;
@@ -743,24 +711,22 @@
         Runnable firstMdnsTask = currentThreadExecutor.getAndClearSubmittedRunnable();
 
         // Process the initial response.
-        MdnsResponse initialResponse =
-                createMockResponse(
-                        serviceInstanceName, "192.168.1.1", 5353, List.of("ABCDE"),
-                        Map.of(), INTERFACE_INDEX, mockNetwork);
-        client.processResponse(initialResponse);
+        client.processResponse(createResponse(
+                serviceInstanceName, "192.168.1.1", 5353, /* subtype= */ "ABCDE",
+                Collections.emptyMap(), TEST_TTL), INTERFACE_INDEX, mockNetwork);
 
         // Clear the scheduled runnable.
         currentThreadExecutor.getAndClearLastScheduledRunnable();
 
         // Simulate the case where the response is under TTL.
-        when(initialResponse.getServiceRecord().getRemainingTTL(anyLong())).thenReturn((long) 1000);
+        doReturn(TEST_ELAPSED_REALTIME + TEST_TTL - 1L).when(mockDecoderClock).elapsedRealtime();
         firstMdnsTask.run();
 
         // Verify removed callback was not called.
         verifyServiceRemovedNoCallback(mockListenerOne);
 
         // Simulate the case where the response is after TTL.
-        when(initialResponse.getServiceRecord().getRemainingTTL(anyLong())).thenReturn((long) 0);
+        doReturn(TEST_ELAPSED_REALTIME + TEST_TTL + 1L).when(mockDecoderClock).elapsedRealtime();
         firstMdnsTask.run();
 
         // Verify removed callback was called.
@@ -773,7 +739,8 @@
             throws Exception {
         final String serviceInstanceName = "service-instance-1";
         client =
-                new MdnsServiceTypeClient(SERVICE_TYPE, mockSocketClient, currentThreadExecutor) {
+                new MdnsServiceTypeClient(SERVICE_TYPE, mockSocketClient, currentThreadExecutor,
+                        mockDecoderClock, mockNetwork) {
                     @Override
                     MdnsPacketWriter createMdnsPacketWriter() {
                         return mockPacketWriter;
@@ -783,17 +750,15 @@
         Runnable firstMdnsTask = currentThreadExecutor.getAndClearSubmittedRunnable();
 
         // Process the initial response.
-        MdnsResponse initialResponse =
-                createMockResponse(
-                        serviceInstanceName, "192.168.1.1", 5353, List.of("ABCDE"),
-                        Map.of(), INTERFACE_INDEX, mockNetwork);
-        client.processResponse(initialResponse);
+        client.processResponse(createResponse(
+                serviceInstanceName, "192.168.1.1", 5353, /* subtype= */ "ABCDE",
+                Collections.emptyMap(), TEST_TTL), INTERFACE_INDEX, mockNetwork);
 
         // Clear the scheduled runnable.
         currentThreadExecutor.getAndClearLastScheduledRunnable();
 
         // Simulate the case where the response is after TTL.
-        when(initialResponse.getServiceRecord().getRemainingTTL(anyLong())).thenReturn((long) 0);
+        doReturn(TEST_ELAPSED_REALTIME + TEST_TTL + 1L).when(mockDecoderClock).elapsedRealtime();
         firstMdnsTask.run();
 
         // Verify removed callback was not called.
@@ -807,7 +772,8 @@
         //MdnsConfigsFlagsImpl.removeServiceAfterTtlExpires.override(true);
         final String serviceInstanceName = "service-instance-1";
         client =
-                new MdnsServiceTypeClient(SERVICE_TYPE, mockSocketClient, currentThreadExecutor) {
+                new MdnsServiceTypeClient(SERVICE_TYPE, mockSocketClient, currentThreadExecutor,
+                        mockDecoderClock, mockNetwork) {
                     @Override
                     MdnsPacketWriter createMdnsPacketWriter() {
                         return mockPacketWriter;
@@ -817,17 +783,15 @@
         Runnable firstMdnsTask = currentThreadExecutor.getAndClearSubmittedRunnable();
 
         // Process the initial response.
-        MdnsResponse initialResponse =
-                createMockResponse(
-                        serviceInstanceName, "192.168.1.1", 5353, List.of("ABCDE"),
-                        Map.of(), INTERFACE_INDEX, mockNetwork);
-        client.processResponse(initialResponse);
+        client.processResponse(createResponse(
+                serviceInstanceName, "192.168.1.1", 5353, /* subtype= */ "ABCDE",
+                Collections.emptyMap(), TEST_TTL), INTERFACE_INDEX, mockNetwork);
 
         // Clear the scheduled runnable.
         currentThreadExecutor.getAndClearLastScheduledRunnable();
 
         // Simulate the case where the response is after TTL.
-        when(initialResponse.getServiceRecord().getRemainingTTL(anyLong())).thenReturn((long) 0);
+        doReturn(TEST_ELAPSED_REALTIME + TEST_TTL + 1L).when(mockDecoderClock).elapsedRealtime();
         firstMdnsTask.run();
 
         // Verify removed callback was called.
@@ -844,56 +808,36 @@
         InOrder inOrder = inOrder(mockListenerOne);
 
         // Process the initial response which is incomplete.
-        final MdnsResponse initialResponse =
-                createResponse(
-                        serviceName,
-                        null,
-                        5353,
-                        "ABCDE" /* subtype */,
-                        Collections.emptyMap(),
-                        INTERFACE_INDEX,
-                        mockNetwork);
-        client.processResponse(initialResponse);
+        final String subtype = "ABCDE";
+        client.processResponse(createResponse(
+                serviceName, null, 5353, subtype,
+                Collections.emptyMap(), TEST_TTL), INTERFACE_INDEX, mockNetwork);
 
         // Process a second response which has ip address to make response become complete.
-        final MdnsResponse secondResponse =
-                createResponse(
-                        serviceName,
-                        ipV4Address,
-                        5353,
-                        "ABCDE" /* subtype */,
-                        Collections.emptyMap(),
-                        INTERFACE_INDEX,
-                        mockNetwork);
-        client.processResponse(secondResponse);
+        client.processResponse(createResponse(
+                serviceName, ipV4Address, 5353, subtype,
+                Collections.emptyMap(), TEST_TTL), INTERFACE_INDEX, mockNetwork);
 
         // Process a third response with a different ip address, port and updated text attributes.
-        final MdnsResponse thirdResponse =
-                createResponse(
-                        serviceName,
-                        ipV6Address,
-                        5354,
-                        "ABCDE" /* subtype */,
-                        Collections.singletonMap("key", "value"),
-                        INTERFACE_INDEX,
-                        mockNetwork);
-        client.processResponse(thirdResponse);
+        client.processResponse(createResponse(
+                serviceName, ipV6Address, 5354, subtype,
+                Collections.singletonMap("key", "value"), TEST_TTL), INTERFACE_INDEX, mockNetwork);
 
-        // Process the last response which is goodbye message.
-        final MdnsResponse lastResponse = mock(MdnsResponse.class);
-        doReturn(serviceName).when(lastResponse).getServiceInstanceName();
-        doReturn(true).when(lastResponse).isGoodbye();
-        client.processResponse(lastResponse);
+        // Process the last response which is goodbye message (with the main type, not subtype).
+        client.processResponse(createResponse(
+                serviceName, ipV6Address, 5354, SERVICE_TYPE_LABELS,
+                Collections.singletonMap("key", "value"), /* ptrTtlMillis= */ 0L),
+                INTERFACE_INDEX, mockNetwork);
 
         // Verify onServiceNameDiscovered was first called for the initial response.
         inOrder.verify(mockListenerOne).onServiceNameDiscovered(serviceInfoCaptor.capture());
         verifyServiceInfo(serviceInfoCaptor.getAllValues().get(0),
                 serviceName,
                 SERVICE_TYPE_LABELS,
-                null /* ipv4Address */,
-                null /* ipv6Address */,
+                List.of() /* ipv4Address */,
+                List.of() /* ipv6Address */,
                 5353 /* port */,
-                Collections.singletonList("ABCDE") /* subTypes */,
+                Collections.singletonList(subtype) /* subTypes */,
                 Collections.singletonMap("key", null) /* attributes */,
                 INTERFACE_INDEX,
                 mockNetwork);
@@ -903,10 +847,10 @@
         verifyServiceInfo(serviceInfoCaptor.getAllValues().get(1),
                 serviceName,
                 SERVICE_TYPE_LABELS,
-                ipV4Address /* ipv4Address */,
-                null /* ipv6Address */,
+                List.of(ipV4Address) /* ipv4Address */,
+                List.of() /* ipv6Address */,
                 5353 /* port */,
-                Collections.singletonList("ABCDE") /* subTypes */,
+                Collections.singletonList(subtype) /* subTypes */,
                 Collections.singletonMap("key", null) /* attributes */,
                 INTERFACE_INDEX,
                 mockNetwork);
@@ -916,10 +860,10 @@
         verifyServiceInfo(serviceInfoCaptor.getAllValues().get(2),
                 serviceName,
                 SERVICE_TYPE_LABELS,
-                ipV4Address /* ipv4Address */,
-                ipV6Address /* ipv6Address */,
+                List.of(ipV4Address) /* ipv4Address */,
+                List.of(ipV6Address) /* ipv6Address */,
                 5354 /* port */,
-                Collections.singletonList("ABCDE") /* subTypes */,
+                Collections.singletonList(subtype) /* subTypes */,
                 Collections.singletonMap("key", "value") /* attributes */,
                 INTERFACE_INDEX,
                 mockNetwork);
@@ -929,8 +873,8 @@
         verifyServiceInfo(serviceInfoCaptor.getAllValues().get(3),
                 serviceName,
                 SERVICE_TYPE_LABELS,
-                ipV4Address /* ipv4Address */,
-                ipV6Address /* ipv6Address */,
+                List.of(ipV4Address) /* ipv4Address */,
+                List.of(ipV6Address) /* ipv6Address */,
                 5354 /* port */,
                 Collections.singletonList("ABCDE") /* subTypes */,
                 Collections.singletonMap("key", "value") /* attributes */,
@@ -942,8 +886,8 @@
         verifyServiceInfo(serviceInfoCaptor.getAllValues().get(4),
                 serviceName,
                 SERVICE_TYPE_LABELS,
-                ipV4Address /* ipv4Address */,
-                ipV6Address /* ipv6Address */,
+                List.of(ipV4Address) /* ipv4Address */,
+                List.of(ipV6Address) /* ipv6Address */,
                 5354 /* port */,
                 Collections.singletonList("ABCDE") /* subTypes */,
                 Collections.singletonMap("key", "value") /* attributes */,
@@ -951,6 +895,103 @@
                 mockNetwork);
     }
 
+    @Test
+    public void testProcessResponse_Resolve() throws Exception {
+        client = new MdnsServiceTypeClient(
+                SERVICE_TYPE, mockSocketClient, currentThreadExecutor, mockNetwork);
+
+        final String instanceName = "service-instance";
+        final String[] hostname = new String[] { "testhost "};
+        final String ipV4Address = "192.0.2.0";
+        final String ipV6Address = "2001:db8::";
+
+        final MdnsSearchOptions resolveOptions = MdnsSearchOptions.newBuilder()
+                .setResolveInstanceName(instanceName).build();
+
+        client.startSendAndReceive(mockListenerOne, resolveOptions);
+        InOrder inOrder = inOrder(mockListenerOne, mockSocketClient);
+
+        // Verify a query for SRV/TXT was sent, but no PTR query
+        final ArgumentCaptor<DatagramPacket> srvTxtQueryCaptor =
+                ArgumentCaptor.forClass(DatagramPacket.class);
+        currentThreadExecutor.getAndClearLastScheduledRunnable().run();
+        // Send twice for IPv4 and IPv6
+        inOrder.verify(mockSocketClient, times(2)).sendUnicastPacket(srvTxtQueryCaptor.capture(),
+                eq(null) /* network */);
+
+        final MdnsPacket srvTxtQueryPacket = MdnsPacket.parse(
+                new MdnsPacketReader(srvTxtQueryCaptor.getValue()));
+        final List<MdnsRecord> srvTxtQuestions = srvTxtQueryPacket.questions;
+
+        final String[] serviceName = Stream.concat(Stream.of(instanceName),
+                Arrays.stream(SERVICE_TYPE_LABELS)).toArray(String[]::new);
+        assertFalse(srvTxtQuestions.stream().anyMatch(q -> q.getType() == MdnsRecord.TYPE_PTR));
+        assertTrue(srvTxtQuestions.stream().anyMatch(q ->
+                q.getType() == MdnsRecord.TYPE_SRV && Arrays.equals(q.name, serviceName)));
+        assertTrue(srvTxtQuestions.stream().anyMatch(q ->
+                q.getType() == MdnsRecord.TYPE_TXT && Arrays.equals(q.name, serviceName)));
+
+        // Process a response with SRV+TXT
+        final MdnsPacket srvTxtResponse = new MdnsPacket(
+                0 /* flags */,
+                Collections.emptyList() /* questions */,
+                List.of(
+                        new MdnsServiceRecord(serviceName, 0L /* receiptTimeMillis */,
+                                true /* cacheFlush */, TEST_TTL, 0 /* servicePriority */,
+                                0 /* serviceWeight */, 1234 /* servicePort */, hostname),
+                        new MdnsTextRecord(serviceName, 0L /* receiptTimeMillis */,
+                                true /* cacheFlush */, TEST_TTL,
+                                Collections.emptyList() /* entries */)),
+                Collections.emptyList() /* authorityRecords */,
+                Collections.emptyList() /* additionalRecords */);
+
+        client.processResponse(srvTxtResponse, INTERFACE_INDEX, mockNetwork);
+
+        // Expect a query for A/AAAA
+        final ArgumentCaptor<DatagramPacket> addressQueryCaptor =
+                ArgumentCaptor.forClass(DatagramPacket.class);
+        currentThreadExecutor.getAndClearLastScheduledRunnable().run();
+        inOrder.verify(mockSocketClient, times(2)).sendMulticastPacket(addressQueryCaptor.capture(),
+                eq(null) /* network */);
+
+        final MdnsPacket addressQueryPacket = MdnsPacket.parse(
+                new MdnsPacketReader(addressQueryCaptor.getValue()));
+        final List<MdnsRecord> addressQueryQuestions = addressQueryPacket.questions;
+        assertTrue(addressQueryQuestions.stream().anyMatch(q ->
+                q.getType() == MdnsRecord.TYPE_A && Arrays.equals(q.name, hostname)));
+        assertTrue(addressQueryQuestions.stream().anyMatch(q ->
+                q.getType() == MdnsRecord.TYPE_AAAA && Arrays.equals(q.name, hostname)));
+
+        // Process a response with address records
+        final MdnsPacket addressResponse = new MdnsPacket(
+                0 /* flags */,
+                Collections.emptyList() /* questions */,
+                List.of(
+                        new MdnsInetAddressRecord(hostname, 0L /* receiptTimeMillis */,
+                                true /* cacheFlush */, TEST_TTL,
+                                InetAddresses.parseNumericAddress(ipV4Address)),
+                        new MdnsInetAddressRecord(hostname, 0L /* receiptTimeMillis */,
+                                true /* cacheFlush */, TEST_TTL,
+                                InetAddresses.parseNumericAddress(ipV6Address))),
+                Collections.emptyList() /* authorityRecords */,
+                Collections.emptyList() /* additionalRecords */);
+
+        inOrder.verify(mockListenerOne, never()).onServiceNameDiscovered(any());
+        client.processResponse(addressResponse, INTERFACE_INDEX, mockNetwork);
+
+        inOrder.verify(mockListenerOne).onServiceFound(serviceInfoCaptor.capture());
+        verifyServiceInfo(serviceInfoCaptor.getValue(),
+                instanceName,
+                SERVICE_TYPE_LABELS,
+                List.of(ipV4Address),
+                List.of(ipV6Address),
+                1234 /* port */,
+                Collections.emptyList() /* subTypes */,
+                Collections.emptyMap() /* attributes */,
+                INTERFACE_INDEX,
+                mockNetwork);
+    }
+
     // verifies that the right query was enqueued with the right delay, and send query by executing
     // the runnable.
     private void verifyAndSendQuery(int index, long timeInMs, boolean expectsUnicastResponse) {
@@ -1029,106 +1070,68 @@
         }
     }
 
-    // Creates a mock mDNS response.
-    private MdnsResponse createMockResponse(
-            @NonNull String serviceInstanceName,
-            @NonNull String host,
-            int port,
-            @NonNull List<String> subtypes,
-            @NonNull Map<String, String> textAttributes,
-            int interfaceIndex,
-            Network network)
-            throws Exception {
-        String[] hostName = new String[]{"hostname"};
-        MdnsServiceRecord serviceRecord = mock(MdnsServiceRecord.class);
-        when(serviceRecord.getServiceHost()).thenReturn(hostName);
-        when(serviceRecord.getServicePort()).thenReturn(port);
-
-        MdnsResponse response = spy(new MdnsResponse(0, interfaceIndex, network));
-
-        MdnsInetAddressRecord inetAddressRecord = mock(MdnsInetAddressRecord.class);
-        if (host.contains(":")) {
-            when(inetAddressRecord.getInet6Address())
-                    .thenReturn((Inet6Address) Inet6Address.getByName(host));
-            response.setInet6AddressRecord(inetAddressRecord);
-        } else {
-            when(inetAddressRecord.getInet4Address())
-                    .thenReturn((Inet4Address) Inet4Address.getByName(host));
-            response.setInet4AddressRecord(inetAddressRecord);
-        }
-
-        MdnsTextRecord textRecord = mock(MdnsTextRecord.class);
-        List<String> textStrings = new ArrayList<>();
-        List<TextEntry> textEntries = new ArrayList<>();
-        for (Map.Entry<String, String> kv : textAttributes.entrySet()) {
-            textStrings.add(kv.getKey() + "=" + kv.getValue());
-            textEntries.add(new TextEntry(kv.getKey(), kv.getValue().getBytes(UTF_8)));
-        }
-        when(textRecord.getStrings()).thenReturn(textStrings);
-        when(textRecord.getEntries()).thenReturn(textEntries);
-
-        response.setServiceRecord(serviceRecord);
-        response.setTextRecord(textRecord);
-
-        doReturn(false).when(response).isGoodbye();
-        doReturn(true).when(response).isComplete();
-        doReturn(serviceInstanceName).when(response).getServiceInstanceName();
-        doReturn(new ArrayList<>(subtypes)).when(response).getSubtypes();
-        return response;
-    }
-
-    // Creates a mDNS response.
-    private MdnsResponse createResponse(
+    private MdnsPacket createResponse(
             @NonNull String serviceInstanceName,
             @Nullable String host,
             int port,
             @NonNull String subtype,
             @NonNull Map<String, String> textAttributes,
-            int interfaceIndex,
-            Network network)
+            long ptrTtlMillis)
             throws Exception {
-        MdnsResponse response = new MdnsResponse(0, interfaceIndex, network);
+        final ArrayList<String> type = new ArrayList<>();
+        type.add(subtype);
+        type.add(MdnsConstants.SUBTYPE_LABEL);
+        type.addAll(Arrays.asList(SERVICE_TYPE_LABELS));
+        return createResponse(serviceInstanceName, host, port, type.toArray(new String[0]),
+                textAttributes, ptrTtlMillis);
+    }
+
+    // Creates a mDNS response.
+    private MdnsPacket createResponse(
+            @NonNull String serviceInstanceName,
+            @Nullable String host,
+            int port,
+            @NonNull String[] type,
+            @NonNull Map<String, String> textAttributes,
+            long ptrTtlMillis) {
+
+        final ArrayList<MdnsRecord> answerRecords = new ArrayList<>();
 
         // Set PTR record
+        final ArrayList<String> serviceNameList = new ArrayList<>();
+        serviceNameList.add(serviceInstanceName);
+        serviceNameList.addAll(Arrays.asList(type));
+        final String[] serviceName = serviceNameList.toArray(new String[0]);
         final MdnsPointerRecord pointerRecord = new MdnsPointerRecord(
-                new String[]{subtype, MdnsConstants.SUBTYPE_LABEL, "test"} /* name */,
-                0L /* receiptTimeMillis */,
+                type,
+                TEST_ELAPSED_REALTIME /* receiptTimeMillis */,
                 false /* cacheFlush */,
-                120000L /* ttlMillis */,
-                new String[]{serviceInstanceName});
-        response.addPointerRecord(pointerRecord);
+                ptrTtlMillis,
+                serviceName);
+        answerRecords.add(pointerRecord);
 
         // Set SRV record.
         final MdnsServiceRecord serviceRecord = new MdnsServiceRecord(
-                new String[] {"service"} /* name */,
-                0L /* receiptTimeMillis */,
+                serviceName,
+                TEST_ELAPSED_REALTIME /* receiptTimeMillis */,
                 false /* cacheFlush */,
-                120000L /* ttlMillis */,
+                TEST_TTL,
                 0 /* servicePriority */,
                 0 /* serviceWeight */,
                 port,
                 new String[]{"hostname"});
-        response.setServiceRecord(serviceRecord);
+        answerRecords.add(serviceRecord);
 
         // Set A/AAAA record.
         if (host != null) {
-            if (InetAddresses.parseNumericAddress(host) instanceof Inet6Address) {
-                final MdnsInetAddressRecord inetAddressRecord = new MdnsInetAddressRecord(
-                        new String[] {"address"} /* name */,
-                        0L /* receiptTimeMillis */,
-                        false /* cacheFlush */,
-                        120000L /* ttlMillis */,
-                        Inet6Address.getByName(host));
-                response.setInet6AddressRecord(inetAddressRecord);
-            } else {
-                final MdnsInetAddressRecord inetAddressRecord = new MdnsInetAddressRecord(
-                        new String[] {"address"} /* name */,
-                        0L /* receiptTimeMillis */,
-                        false /* cacheFlush */,
-                        120000L /* ttlMillis */,
-                        Inet4Address.getByName(host));
-                response.setInet4AddressRecord(inetAddressRecord);
-            }
+            final InetAddress addr = InetAddresses.parseNumericAddress(host);
+            final MdnsInetAddressRecord inetAddressRecord = new MdnsInetAddressRecord(
+                    new String[] {"hostname"} /* name */,
+                    TEST_ELAPSED_REALTIME /* receiptTimeMillis */,
+                    false /* cacheFlush */,
+                    TEST_TTL,
+                    addr);
+            answerRecords.add(inetAddressRecord);
         }
 
         // Set TXT record.
@@ -1137,12 +1140,18 @@
             textEntries.add(new TextEntry(kv.getKey(), kv.getValue().getBytes(UTF_8)));
         }
         final MdnsTextRecord textRecord = new MdnsTextRecord(
-                new String[] {"text"} /* name */,
-                0L /* receiptTimeMillis */,
+                serviceName,
+                TEST_ELAPSED_REALTIME /* receiptTimeMillis */,
                 false /* cacheFlush */,
-                120000L /* ttlMillis */,
+                TEST_TTL,
                 textEntries);
-        response.setTextRecord(textRecord);
-        return response;
+        answerRecords.add(textRecord);
+        return new MdnsPacket(
+                0 /* flags */,
+                Collections.emptyList() /* questions */,
+                answerRecords,
+                Collections.emptyList() /* authorityRecords */,
+                Collections.emptyList() /* additionalRecords */
+        );
     }
 }
\ No newline at end of file
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketClientTests.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketClientTests.java
index 1d61cd3..abb1747 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketClientTests.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketClientTests.java
@@ -18,13 +18,11 @@
 
 import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
 
-import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.argThat;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.never;
@@ -48,7 +46,6 @@
 import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
 import org.mockito.ArgumentMatchers;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
@@ -373,7 +370,7 @@
         mdnsClient.startDiscovery();
 
         verify(mockCallback, timeout(TIMEOUT).atLeast(1))
-                .onResponseReceived(any(MdnsResponse.class));
+                .onResponseReceived(any(MdnsPacket.class), anyInt(), any());
     }
 
     @Test
@@ -382,7 +379,7 @@
         mdnsClient.startDiscovery();
 
         verify(mockCallback, timeout(TIMEOUT).atLeastOnce())
-                .onResponseReceived(any(MdnsResponse.class));
+                .onResponseReceived(any(MdnsPacket.class), anyInt(), any());
 
         mdnsClient.stopDiscovery();
     }
@@ -415,7 +412,8 @@
         mdnsClient.startDiscovery();
 
         verify(mockCallback, timeout(TIMEOUT).atLeast(1))
-                .onFailedToParseMdnsResponse(anyInt(), eq(MdnsResponseErrorCode.ERROR_END_OF_FILE));
+                .onFailedToParseMdnsResponse(
+                        anyInt(), eq(MdnsResponseErrorCode.ERROR_END_OF_FILE), any());
 
         mdnsClient.stopDiscovery();
     }
@@ -436,7 +434,8 @@
         mdnsClient.startDiscovery();
 
         verify(mockCallback, timeout(TIMEOUT).atLeast(1))
-                .onFailedToParseMdnsResponse(1, MdnsResponseErrorCode.ERROR_END_OF_FILE);
+                .onFailedToParseMdnsResponse(
+                        eq(1), eq(MdnsResponseErrorCode.ERROR_END_OF_FILE), any());
 
         mdnsClient.stopDiscovery();
     }
@@ -514,7 +513,7 @@
         mdnsClient.startDiscovery();
 
         verify(mockCallback, timeout(TIMEOUT).atLeastOnce())
-                .onResponseReceived(argThat(response -> response.getInterfaceIndex() == 21));
+                .onResponseReceived(any(), eq(21), any());
     }
 
     @Test
@@ -536,11 +535,7 @@
         mdnsClient.setCallback(mockCallback);
         mdnsClient.startDiscovery();
 
-        ArgumentCaptor<MdnsResponse> mdnsResponseCaptor =
-                ArgumentCaptor.forClass(MdnsResponse.class);
         verify(mockMulticastSocket, never()).getInterfaceIndex();
-        verify(mockCallback, timeout(TIMEOUT).atLeast(1))
-                .onResponseReceived(mdnsResponseCaptor.capture());
-        assertEquals(-1, mdnsResponseCaptor.getValue().getInterfaceIndex());
+        verify(mockCallback, timeout(TIMEOUT).atLeast(1)).onResponseReceived(any(), eq(-1), any());
     }
 }
\ No newline at end of file
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketProviderTest.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketProviderTest.java
index 635b296..b9cb255 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketProviderTest.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketProviderTest.java
@@ -27,6 +27,7 @@
 import static org.mockito.Mockito.doCallRealMethod;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
@@ -109,12 +110,15 @@
         final HandlerThread thread = new HandlerThread("MdnsSocketProviderTest");
         thread.start();
         mHandler = new Handler(thread.getLooper());
+        mSocketProvider = new MdnsSocketProvider(mContext, thread.getLooper(), mDeps);
+    }
 
+    private void startMonitoringSockets() {
         final ArgumentCaptor<NetworkCallback> nwCallbackCaptor =
                 ArgumentCaptor.forClass(NetworkCallback.class);
         final ArgumentCaptor<TetheringEventCallback> teCallbackCaptor =
                 ArgumentCaptor.forClass(TetheringEventCallback.class);
-        mSocketProvider = new MdnsSocketProvider(mContext, thread.getLooper(), mDeps);
+
         mHandler.post(mSocketProvider::startMonitoringSockets);
         HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
         verify(mCm).registerNetworkCallback(any(), nwCallbackCaptor.capture(), any());
@@ -205,6 +209,8 @@
 
     @Test
     public void testSocketRequestAndUnrequestSocket() {
+        startMonitoringSockets();
+
         final TestSocketCallback testCallback1 = new TestSocketCallback();
         mHandler.post(() -> mSocketProvider.requestSocket(TEST_NETWORK, testCallback1));
         HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
@@ -275,6 +281,8 @@
 
     @Test
     public void testAddressesChanged() throws Exception {
+        startMonitoringSockets();
+
         final TestSocketCallback testCallback = new TestSocketCallback();
         mHandler.post(() -> mSocketProvider.requestSocket(TEST_NETWORK, testCallback));
         HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
@@ -297,4 +305,53 @@
         testCallback.expectedAddressesChangedForNetwork(
                 TEST_NETWORK, List.of(LINKADDRV4, LINKADDRV6));
     }
+
+    @Test
+    public void testStartAndStopMonitoringSockets() {
+        // Stop monitoring sockets before start. Should not unregister any network callback.
+        mHandler.post(mSocketProvider::requestStopWhenInactive);
+        HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+        verify(mCm, never()).unregisterNetworkCallback(any(NetworkCallback.class));
+        verify(mTm, never()).unregisterTetheringEventCallback(any(TetheringEventCallback.class));
+
+        // Start sockets monitoring.
+        startMonitoringSockets();
+        // Request a socket then unrequest it. Expect no network callback unregistration.
+        final TestSocketCallback testCallback = new TestSocketCallback();
+        mHandler.post(() -> mSocketProvider.requestSocket(TEST_NETWORK, testCallback));
+        HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+        testCallback.expectedNoCallback();
+        mHandler.post(()-> mSocketProvider.unrequestSocket(testCallback));
+        HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+        verify(mCm, never()).unregisterNetworkCallback(any(NetworkCallback.class));
+        verify(mTm, never()).unregisterTetheringEventCallback(any(TetheringEventCallback.class));
+        // Request stop and it should unregister network callback immediately because there is no
+        // socket request.
+        mHandler.post(mSocketProvider::requestStopWhenInactive);
+        HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+        verify(mCm, times(1)).unregisterNetworkCallback(any(NetworkCallback.class));
+        verify(mTm, times(1)).unregisterTetheringEventCallback(any(TetheringEventCallback.class));
+
+        // Start sockets monitoring and request a socket again.
+        mHandler.post(mSocketProvider::startMonitoringSockets);
+        HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+        verify(mCm, times(2)).registerNetworkCallback(any(), any(NetworkCallback.class), any());
+        verify(mTm, times(2)).registerTetheringEventCallback(
+                any(), any(TetheringEventCallback.class));
+        final TestSocketCallback testCallback2 = new TestSocketCallback();
+        mHandler.post(() -> mSocketProvider.requestSocket(TEST_NETWORK, testCallback2));
+        HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+        testCallback2.expectedNoCallback();
+        // Try to stop monitoring sockets but should be ignored and wait until all socket are
+        // unrequested.
+        mHandler.post(mSocketProvider::requestStopWhenInactive);
+        HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+        verify(mCm, times(1)).unregisterNetworkCallback(any(NetworkCallback.class));
+        verify(mTm, times(1)).unregisterTetheringEventCallback(any());
+        // Unrequest the socket then network callbacks should be unregistered.
+        mHandler.post(()-> mSocketProvider.unrequestSocket(testCallback2));
+        HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+        verify(mCm, times(2)).unregisterNetworkCallback(any(NetworkCallback.class));
+        verify(mTm, times(2)).unregisterTetheringEventCallback(any(TetheringEventCallback.class));
+    }
 }
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
index 13a6a6f..8046263 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
@@ -82,7 +82,6 @@
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
@@ -395,10 +394,6 @@
         verify(mNetd).registerUnsolicitedEventListener(alertObserver.capture());
         mAlertObserver = alertObserver.getValue();
 
-        // Make augmentWithStackedInterfaces returns the interfaces that was passed to it.
-        doAnswer(inv -> ((String[]) inv.getArgument(0)).clone())
-                .when(mStatsFactory).augmentWithStackedInterfaces(any());
-
         // Catch TetheringEventCallback during systemReady().
         ArgumentCaptor<TetheringManager.TetheringEventCallback> tetheringEventCbCaptor =
                 ArgumentCaptor.forClass(TetheringManager.TetheringEventCallback.class);
diff --git a/tests/unit/res/xml/self_certified_capabilities_bandwidth.xml b/tests/unit/res/xml/self_certified_capabilities_bandwidth.xml
new file mode 100644
index 0000000..01fdca1
--- /dev/null
+++ b/tests/unit/res/xml/self_certified_capabilities_bandwidth.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<network-capabilities-declaration xmlns:android="http://schemas.android.com/apk/res/android">
+    <uses-network-capability android:name="NET_CAPABILITY_PRIORITIZE_BANDWIDTH"/>
+</network-capabilities-declaration>
diff --git a/tests/unit/res/xml/self_certified_capabilities_both.xml b/tests/unit/res/xml/self_certified_capabilities_both.xml
new file mode 100644
index 0000000..4066be2
--- /dev/null
+++ b/tests/unit/res/xml/self_certified_capabilities_both.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<network-capabilities-declaration xmlns:android="http://schemas.android.com/apk/res/android">
+    <uses-network-capability android:name="NET_CAPABILITY_PRIORITIZE_LATENCY"/>
+    <uses-network-capability android:name="NET_CAPABILITY_PRIORITIZE_BANDWIDTH"/>
+</network-capabilities-declaration>
diff --git a/tests/unit/res/xml/self_certified_capabilities_latency.xml b/tests/unit/res/xml/self_certified_capabilities_latency.xml
new file mode 100644
index 0000000..1c4a0e0
--- /dev/null
+++ b/tests/unit/res/xml/self_certified_capabilities_latency.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<network-capabilities-declaration xmlns:android="http://schemas.android.com/apk/res/android">
+    <uses-network-capability android:name="NET_CAPABILITY_PRIORITIZE_LATENCY"/>
+</network-capabilities-declaration>
diff --git a/tests/unit/res/xml/self_certified_capabilities_other.xml b/tests/unit/res/xml/self_certified_capabilities_other.xml
new file mode 100644
index 0000000..5b6649c
--- /dev/null
+++ b/tests/unit/res/xml/self_certified_capabilities_other.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<network-capabilities-declaration xmlns:android="http://schemas.android.com/apk/res/android">
+    <uses-network-capability android:name="other"/>
+</network-capabilities-declaration>
diff --git a/tests/unit/res/xml/self_certified_capabilities_wrong_declaration.xml b/tests/unit/res/xml/self_certified_capabilities_wrong_declaration.xml
new file mode 100644
index 0000000..6082356
--- /dev/null
+++ b/tests/unit/res/xml/self_certified_capabilities_wrong_declaration.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<network-capabilities-declaration1 xmlns:android="http://schemas.android.com/apk/res/android">
+    <uses-network-capability android:name="NET_CAPABILITY_PRIORITIZE_LATENCY"/>
+</network-capabilities-declaration1>
diff --git a/tests/unit/res/xml/self_certified_capabilities_wrong_tag.xml b/tests/unit/res/xml/self_certified_capabilities_wrong_tag.xml
new file mode 100644
index 0000000..c9ecc0b
--- /dev/null
+++ b/tests/unit/res/xml/self_certified_capabilities_wrong_tag.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<network-capabilities-declaration xmlns:android="http://schemas.android.com/apk/res/android">
+    <uses-network-capability1 android:name="NET_CAPABILITY_PRIORITIZE_LATENCY"/>
+    <uses-network-capability android:name="NET_CAPABILITY_PRIORITIZE_LATENCY"/>
+    <uses-network-capability android:name="NET_CAPABILITY_PRIORITIZE_BANDWIDTH"/>
+</network-capabilities-declaration>
diff --git a/tools/gn2bp/Android.bp.swp b/tools/gn2bp/Android.bp.swp
index 0ff8284..19901fa 100644
--- a/tools/gn2bp/Android.bp.swp
+++ b/tools/gn2bp/Android.bp.swp
@@ -16,7 +16,7 @@
 
 build = ["Android.extras.bp"]
 
-// GN: PACKAGE
+// The actual license can be found in Android.extras.bp
 package {
     default_applicable_licenses: [
         "external_cronet_license",
@@ -36,6 +36,7 @@
         "components/cronet/android/api/src/android/net/http/ExperimentalBidirectionalStream.java",
         "components/cronet/android/api/src/android/net/http/ExperimentalHttpEngine.java",
         "components/cronet/android/api/src/android/net/http/ExperimentalUrlRequest.java",
+        "components/cronet/android/api/src/android/net/http/HeaderBlock.java",
         "components/cronet/android/api/src/android/net/http/HttpEngine.java",
         "components/cronet/android/api/src/android/net/http/HttpException.java",
         "components/cronet/android/api/src/android/net/http/IHttpEngineBuilder.java",
@@ -1418,6 +1419,8 @@
         "base/vlog.cc",
     ],
     shared_libs: [
+        "//external/cronet/third_party/boringssl:libcrypto",
+        "//external/cronet/third_party/boringssl:libssl",
         "libandroid",
         "liblog",
     ],
@@ -1426,7 +1429,6 @@
         "cronet_aml_base_base_static",
         "cronet_aml_base_third_party_double_conversion_double_conversion",
         "cronet_aml_base_third_party_dynamic_annotations_dynamic_annotations",
-        "cronet_aml_third_party_boringssl_boringssl",
         "cronet_aml_third_party_icu_icui18n",
         "cronet_aml_third_party_icu_icuuc_private",
         "cronet_aml_third_party_libevent_libevent",
@@ -3404,7 +3406,7 @@
 cc_genrule {
     name: "cronet_aml_base_build_date",
     cmd: "$(location build/write_build_date_header.py) $(out) " +
-         "1674644139",
+         "1678293768",
     out: [
         "base/generated_build_date.h",
     ],
@@ -3420,7 +3422,7 @@
 cc_genrule {
     name: "cronet_aml_base_build_date__testing",
     cmd: "$(location build/write_build_date_header.py) $(out) " +
-         "1674644139",
+         "1678293768",
     host_supported: true,
     out: [
         "base/generated_build_date.h",
@@ -6433,6 +6435,8 @@
         "components/cronet/android/cronet_jni.cc",
     ],
     shared_libs: [
+        "//external/cronet/third_party/boringssl:libcrypto",
+        "//external/cronet/third_party/boringssl:libssl",
         "libandroid",
         "liblog",
         "libz",
@@ -6449,7 +6453,6 @@
         "cronet_aml_net_preload_decoder",
         "cronet_aml_net_third_party_quiche_quiche",
         "cronet_aml_net_uri_template",
-        "cronet_aml_third_party_boringssl_boringssl",
         "cronet_aml_third_party_brotli_common",
         "cronet_aml_third_party_brotli_dec",
         "cronet_aml_third_party_icu_icui18n",
@@ -6865,6 +6868,7 @@
         "components/cronet/android/java/src/org/chromium/net/impl/CronetUploadDataStream.java",
         "components/cronet/android/java/src/org/chromium/net/impl/CronetUrlRequest.java",
         "components/cronet/android/java/src/org/chromium/net/impl/CronetUrlRequestContext.java",
+        "components/cronet/android/java/src/org/chromium/net/impl/HeaderBlockImpl.java",
         "components/cronet/android/java/src/org/chromium/net/impl/NativeCronetEngineBuilderImpl.java",
         "components/cronet/android/java/src/org/chromium/net/impl/NetworkExceptionImpl.java",
         "components/cronet/android/java/src/org/chromium/net/impl/NoOpLogger.java",
@@ -7118,6 +7122,7 @@
         "components/cronet/android/java/src/org/chromium/net/impl/CronetUploadDataStream.java",
         "components/cronet/android/java/src/org/chromium/net/impl/CronetUrlRequest.java",
         "components/cronet/android/java/src/org/chromium/net/impl/CronetUrlRequestContext.java",
+        "components/cronet/android/java/src/org/chromium/net/impl/HeaderBlockImpl.java",
         "components/cronet/android/java/src/org/chromium/net/impl/NativeCronetEngineBuilderImpl.java",
         "components/cronet/android/java/src/org/chromium/net/impl/NetworkExceptionImpl.java",
         "components/cronet/android/java/src/org/chromium/net/impl/NoOpLogger.java",
@@ -7367,6 +7372,7 @@
         "components/cronet/android/java/src/org/chromium/net/impl/CronetUploadDataStream.java",
         "components/cronet/android/java/src/org/chromium/net/impl/CronetUrlRequest.java",
         "components/cronet/android/java/src/org/chromium/net/impl/CronetUrlRequestContext.java",
+        "components/cronet/android/java/src/org/chromium/net/impl/HeaderBlockImpl.java",
         "components/cronet/android/java/src/org/chromium/net/impl/NativeCronetEngineBuilderImpl.java",
         "components/cronet/android/java/src/org/chromium/net/impl/NetworkExceptionImpl.java",
         "components/cronet/android/java/src/org/chromium/net/impl/NoOpLogger.java",
@@ -7614,6 +7620,7 @@
         "components/cronet/android/java/src/org/chromium/net/impl/CronetUploadDataStream.java",
         "components/cronet/android/java/src/org/chromium/net/impl/CronetUrlRequest.java",
         "components/cronet/android/java/src/org/chromium/net/impl/CronetUrlRequestContext.java",
+        "components/cronet/android/java/src/org/chromium/net/impl/HeaderBlockImpl.java",
         "components/cronet/android/java/src/org/chromium/net/impl/NativeCronetEngineBuilderImpl.java",
         "components/cronet/android/java/src/org/chromium/net/impl/NetworkExceptionImpl.java",
         "components/cronet/android/java/src/org/chromium/net/impl/NoOpLogger.java",
@@ -7705,6 +7712,8 @@
         "components/cronet/android/url_request_error.cc",
     ],
     shared_libs: [
+        "//external/cronet/third_party/boringssl:libcrypto",
+        "//external/cronet/third_party/boringssl:libssl",
         "libandroid",
         "liblog",
         "libz",
@@ -7721,7 +7730,6 @@
         "cronet_aml_net_preload_decoder",
         "cronet_aml_net_third_party_quiche_quiche",
         "cronet_aml_net_uri_template",
-        "cronet_aml_third_party_boringssl_boringssl",
         "cronet_aml_third_party_brotli_common",
         "cronet_aml_third_party_brotli_dec",
         "cronet_aml_third_party_icu_icui18n",
@@ -8442,6 +8450,8 @@
         "components/cronet/url_request_context_config.cc",
     ],
     shared_libs: [
+        "//external/cronet/third_party/boringssl:libcrypto",
+        "//external/cronet/third_party/boringssl:libssl",
         "libandroid",
         "liblog",
         "libz",
@@ -8458,7 +8468,6 @@
         "cronet_aml_net_preload_decoder",
         "cronet_aml_net_third_party_quiche_quiche",
         "cronet_aml_net_uri_template",
-        "cronet_aml_third_party_boringssl_boringssl",
         "cronet_aml_third_party_brotli_common",
         "cronet_aml_third_party_brotli_dec",
         "cronet_aml_third_party_icu_icui18n",
@@ -8868,6 +8877,8 @@
         "components/cronet/metrics_util.cc",
     ],
     shared_libs: [
+        "//external/cronet/third_party/boringssl:libcrypto",
+        "//external/cronet/third_party/boringssl:libssl",
         "libandroid",
         "liblog",
     ],
@@ -8877,7 +8888,6 @@
         "cronet_aml_base_base_static",
         "cronet_aml_base_third_party_double_conversion_double_conversion",
         "cronet_aml_base_third_party_dynamic_annotations_dynamic_annotations",
-        "cronet_aml_third_party_boringssl_boringssl",
         "cronet_aml_third_party_icu_icui18n",
         "cronet_aml_third_party_icu_icuuc_private",
         "cronet_aml_third_party_libevent_libevent",
@@ -9044,6 +9054,8 @@
         "components/metrics/library_support/histogram_manager.cc",
     ],
     shared_libs: [
+        "//external/cronet/third_party/boringssl:libcrypto",
+        "//external/cronet/third_party/boringssl:libssl",
         "libandroid",
         "liblog",
         "libprotobuf-cpp-lite",
@@ -9054,7 +9066,6 @@
         "cronet_aml_base_base_static",
         "cronet_aml_base_third_party_double_conversion_double_conversion",
         "cronet_aml_base_third_party_dynamic_annotations_dynamic_annotations",
-        "cronet_aml_third_party_boringssl_boringssl",
         "cronet_aml_third_party_icu_icui18n",
         "cronet_aml_third_party_icu_icuuc_private",
         "cronet_aml_third_party_libevent_libevent",
@@ -9326,6 +9337,8 @@
         "components/prefs/writeable_pref_store.cc",
     ],
     shared_libs: [
+        "//external/cronet/third_party/boringssl:libcrypto",
+        "//external/cronet/third_party/boringssl:libssl",
         "libandroid",
         "liblog",
     ],
@@ -9335,7 +9348,6 @@
         "cronet_aml_base_base_static",
         "cronet_aml_base_third_party_double_conversion_double_conversion",
         "cronet_aml_base_third_party_dynamic_annotations_dynamic_annotations",
-        "cronet_aml_third_party_boringssl_boringssl",
         "cronet_aml_third_party_icu_icui18n",
         "cronet_aml_third_party_icu_icuuc_private",
         "cronet_aml_third_party_libevent_libevent",
@@ -9849,6 +9861,8 @@
         "crypto/unexportable_key_metrics.cc",
     ],
     shared_libs: [
+        "//external/cronet/third_party/boringssl:libcrypto",
+        "//external/cronet/third_party/boringssl:libssl",
         "libandroid",
         "liblog",
     ],
@@ -9858,7 +9872,6 @@
         "cronet_aml_base_base_static",
         "cronet_aml_base_third_party_double_conversion_double_conversion",
         "cronet_aml_base_third_party_dynamic_annotations_dynamic_annotations",
-        "cronet_aml_third_party_boringssl_boringssl",
         "cronet_aml_third_party_icu_icui18n",
         "cronet_aml_third_party_icu_icuuc_private",
         "cronet_aml_third_party_libevent_libevent",
@@ -10172,6 +10185,115 @@
     },
 }
 
+// GN: //crypto:test_support__testing
+cc_library_static {
+    name: "cronet_aml_crypto_test_support__testing",
+    srcs: [
+        "crypto/scoped_mock_unexportable_key_provider.cc",
+    ],
+    shared_libs: [
+        "libandroid",
+        "liblog",
+    ],
+    static_libs: [
+        "cronet_aml_base_allocator_partition_allocator_partition_alloc__testing",
+        "cronet_aml_base_base__testing",
+        "cronet_aml_base_base_static__testing",
+        "cronet_aml_base_third_party_double_conversion_double_conversion__testing",
+        "cronet_aml_base_third_party_dynamic_annotations_dynamic_annotations__testing",
+        "cronet_aml_crypto_crypto__testing",
+        "cronet_aml_third_party_boringssl_boringssl__testing",
+        "cronet_aml_third_party_icu_icui18n__testing",
+        "cronet_aml_third_party_icu_icuuc_private__testing",
+        "cronet_aml_third_party_libevent_libevent__testing",
+        "cronet_aml_third_party_modp_b64_modp_b64__testing",
+    ],
+    defaults: [
+        "cronet_aml_defaults",
+    ],
+    cflags: [
+        "-DANDROID",
+        "-DANDROID_NDK_VERSION_ROLL=r23_1",
+        "-DCR_CLANG_REVISION=\"llvmorg-16-init-6578-g0d30e92f-2\"",
+        "-DCR_LIBCXX_REVISION=64d36e572d3f9719c5d75011a718f33f11126851",
+        "-DDYNAMIC_ANNOTATIONS_ENABLED=0",
+        "-DHAVE_SYS_UIO_H",
+        "-DNDEBUG",
+        "-DNO_UNWIND_TABLES",
+        "-DNVALGRIND",
+        "-DOFFICIAL_BUILD",
+        "-D_FORTIFY_SOURCE=2",
+        "-D_GNU_SOURCE",
+        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
+        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
+        "-D__STDC_CONSTANT_MACROS",
+        "-D__STDC_FORMAT_MACROS",
+        "-Oz",
+        "-fdata-sections",
+        "-ffunction-sections",
+        "-fno-asynchronous-unwind-tables",
+        "-fno-unwind-tables",
+        "-fvisibility-inlines-hidden",
+        "-fvisibility=hidden",
+        "-g1",
+    ],
+    local_include_dirs: [
+        "./",
+        "buildtools/third_party/libc++/",
+        "buildtools/third_party/libc++/trunk/include",
+        "buildtools/third_party/libc++abi/trunk/include",
+        "third_party/abseil-cpp/",
+        "third_party/boringssl/src/include/",
+    ],
+    cpp_std: "c++17",
+    ldflags: [
+        "-Wl,--as-needed",
+        "-Wl,--gc-sections",
+        "-Wl,--icf=all",
+        "-Wl,--script,external/cronet/base/android/library_loader/anchor_functions.lds",
+        "-Wl,-wrap,asprintf",
+        "-Wl,-wrap,calloc",
+        "-Wl,-wrap,free",
+        "-Wl,-wrap,getcwd",
+        "-Wl,-wrap,malloc",
+        "-Wl,-wrap,malloc_usable_size",
+        "-Wl,-wrap,memalign",
+        "-Wl,-wrap,posix_memalign",
+        "-Wl,-wrap,pvalloc",
+        "-Wl,-wrap,realloc",
+        "-Wl,-wrap,realpath",
+        "-Wl,-wrap,strdup",
+        "-Wl,-wrap,strndup",
+        "-Wl,-wrap,valloc",
+        "-Wl,-wrap,vasprintf",
+    ],
+    target: {
+        android_arm: {
+            cflags: [
+                "-fstack-protector",
+            ],
+        },
+        android_arm64: {
+            cflags: [
+                "-fstack-protector",
+                "-mno-outline",
+                "-mno-outline-atomics",
+            ],
+        },
+        android_x86: {
+            cflags: [
+                "-msse3",
+            ],
+        },
+        android_x86_64: {
+            cflags: [
+                "-fstack-protector",
+                "-msse3",
+            ],
+        },
+    },
+}
+
 // GN: //gn:default_deps
 cc_defaults {
     name: "cronet_aml_defaults",
@@ -10410,6 +10532,7 @@
         "components/cronet/android/java/src/org/chromium/net/impl/CronetUploadDataStream.java",
         "components/cronet/android/java/src/org/chromium/net/impl/CronetUrlRequest.java",
         "components/cronet/android/java/src/org/chromium/net/impl/CronetUrlRequestContext.java",
+        "components/cronet/android/java/src/org/chromium/net/impl/HeaderBlockImpl.java",
         "components/cronet/android/java/src/org/chromium/net/impl/NativeCronetEngineBuilderImpl.java",
         "components/cronet/android/java/src/org/chromium/net/impl/NetworkExceptionImpl.java",
         "components/cronet/android/java/src/org/chromium/net/impl/NoOpLogger.java",
@@ -10687,6 +10810,7 @@
         "components/cronet/android/java/src/org/chromium/net/impl/CronetUploadDataStream.java",
         "components/cronet/android/java/src/org/chromium/net/impl/CronetUrlRequest.java",
         "components/cronet/android/java/src/org/chromium/net/impl/CronetUrlRequestContext.java",
+        "components/cronet/android/java/src/org/chromium/net/impl/HeaderBlockImpl.java",
         "components/cronet/android/java/src/org/chromium/net/impl/NativeCronetEngineBuilderImpl.java",
         "components/cronet/android/java/src/org/chromium/net/impl/NetworkExceptionImpl.java",
         "components/cronet/android/java/src/org/chromium/net/impl/NoOpLogger.java",
@@ -10861,33 +10985,92 @@
     ],
 }
 
+// GN: //net/base/registry_controlled_domains:lookup_strings_test_sets__testing
+cc_genrule {
+    name: "cronet_aml_net_base_registry_controlled_domains_lookup_strings_test_sets__testing",
+    cmd: "$(location net/tools/dafsa/make_dafsa.py) $(location net/base/registry_controlled_domains/effective_tld_names.gperf) " +
+         "$(location net/base/registry_controlled_domains/effective_tld_names-inc.cc) " +
+         "&& " +
+         "python3 $(location net/tools/dafsa/make_dafsa.py) " +
+         "$(location net/base/registry_controlled_domains/effective_tld_names_unittest1.gperf) " +
+         "$(location net/base/registry_controlled_domains/effective_tld_names_unittest1-inc.cc) " +
+         "&& " +
+         "python3 $(location net/tools/dafsa/make_dafsa.py) " +
+         "$(location net/base/registry_controlled_domains/effective_tld_names_unittest2.gperf) " +
+         "$(location net/base/registry_controlled_domains/effective_tld_names_unittest2-inc.cc) " +
+         "&& " +
+         "python3 $(location net/tools/dafsa/make_dafsa.py) " +
+         "$(location net/base/registry_controlled_domains/effective_tld_names_unittest3.gperf) " +
+         "$(location net/base/registry_controlled_domains/effective_tld_names_unittest3-inc.cc) " +
+         "&& " +
+         "python3 $(location net/tools/dafsa/make_dafsa.py) " +
+         "$(location net/base/registry_controlled_domains/effective_tld_names_unittest4.gperf) " +
+         "$(location net/base/registry_controlled_domains/effective_tld_names_unittest4-inc.cc) " +
+         "&& " +
+         "python3 $(location net/tools/dafsa/make_dafsa.py) " +
+         "$(location net/base/registry_controlled_domains/effective_tld_names_unittest5.gperf) " +
+         "$(location net/base/registry_controlled_domains/effective_tld_names_unittest5-inc.cc) " +
+         "&& " +
+         "python3 $(location net/tools/dafsa/make_dafsa.py) " +
+         "$(location net/base/registry_controlled_domains/effective_tld_names_unittest6.gperf) " +
+         "$(location net/base/registry_controlled_domains/effective_tld_names_unittest6-inc.cc)",
+    out: [
+        "net/base/registry_controlled_domains/effective_tld_names-inc.cc",
+        "net/base/registry_controlled_domains/effective_tld_names_unittest1-inc.cc",
+        "net/base/registry_controlled_domains/effective_tld_names_unittest2-inc.cc",
+        "net/base/registry_controlled_domains/effective_tld_names_unittest3-inc.cc",
+        "net/base/registry_controlled_domains/effective_tld_names_unittest4-inc.cc",
+        "net/base/registry_controlled_domains/effective_tld_names_unittest5-inc.cc",
+        "net/base/registry_controlled_domains/effective_tld_names_unittest6-inc.cc",
+    ],
+    tool_files: [
+        "net/base/registry_controlled_domains/effective_tld_names.gperf",
+        "net/base/registry_controlled_domains/effective_tld_names_unittest1.gperf",
+        "net/base/registry_controlled_domains/effective_tld_names_unittest2.gperf",
+        "net/base/registry_controlled_domains/effective_tld_names_unittest3.gperf",
+        "net/base/registry_controlled_domains/effective_tld_names_unittest4.gperf",
+        "net/base/registry_controlled_domains/effective_tld_names_unittest5.gperf",
+        "net/base/registry_controlled_domains/effective_tld_names_unittest6.gperf",
+        "net/tools/dafsa/make_dafsa.py",
+    ],
+    apex_available: [
+        "com.android.tethering",
+    ],
+}
+
 // GN: //net/base/registry_controlled_domains:registry_controlled_domains
 cc_genrule {
     name: "cronet_aml_net_base_registry_controlled_domains_registry_controlled_domains",
     cmd: "$(location net/tools/dafsa/make_dafsa.py) --reverse " +
          "$(location net/base/registry_controlled_domains/effective_tld_names.gperf) " +
          "$(location net/base/registry_controlled_domains/effective_tld_names-reversed-inc.cc) " +
-         "&& python3 $(location net/tools/dafsa/make_dafsa.py) " +
+         "&& " +
+         "python3 $(location net/tools/dafsa/make_dafsa.py) " +
          "--reverse " +
          "$(location net/base/registry_controlled_domains/effective_tld_names_unittest1.gperf) " +
          "$(location net/base/registry_controlled_domains/effective_tld_names_unittest1-reversed-inc.cc) " +
-         "&& python3 $(location net/tools/dafsa/make_dafsa.py) " +
+         "&& " +
+         "python3 $(location net/tools/dafsa/make_dafsa.py) " +
          "--reverse " +
          "$(location net/base/registry_controlled_domains/effective_tld_names_unittest2.gperf) " +
          "$(location net/base/registry_controlled_domains/effective_tld_names_unittest2-reversed-inc.cc) " +
-         "&& python3 $(location net/tools/dafsa/make_dafsa.py) " +
+         "&& " +
+         "python3 $(location net/tools/dafsa/make_dafsa.py) " +
          "--reverse " +
          "$(location net/base/registry_controlled_domains/effective_tld_names_unittest3.gperf) " +
          "$(location net/base/registry_controlled_domains/effective_tld_names_unittest3-reversed-inc.cc) " +
-         "&& python3 $(location net/tools/dafsa/make_dafsa.py) " +
+         "&& " +
+         "python3 $(location net/tools/dafsa/make_dafsa.py) " +
          "--reverse " +
          "$(location net/base/registry_controlled_domains/effective_tld_names_unittest4.gperf) " +
          "$(location net/base/registry_controlled_domains/effective_tld_names_unittest4-reversed-inc.cc) " +
-         "&& python3 $(location net/tools/dafsa/make_dafsa.py) " +
+         "&& " +
+         "python3 $(location net/tools/dafsa/make_dafsa.py) " +
          "--reverse " +
          "$(location net/base/registry_controlled_domains/effective_tld_names_unittest5.gperf) " +
          "$(location net/base/registry_controlled_domains/effective_tld_names_unittest5-reversed-inc.cc) " +
-         "&& python3 $(location net/tools/dafsa/make_dafsa.py) " +
+         "&& " +
+         "python3 $(location net/tools/dafsa/make_dafsa.py) " +
          "--reverse " +
          "$(location net/base/registry_controlled_domains/effective_tld_names_unittest6.gperf) " +
          "$(location net/base/registry_controlled_domains/effective_tld_names_unittest6-reversed-inc.cc)",
@@ -10921,27 +11104,33 @@
     cmd: "$(location net/tools/dafsa/make_dafsa.py) --reverse " +
          "$(location net/base/registry_controlled_domains/effective_tld_names.gperf) " +
          "$(location net/base/registry_controlled_domains/effective_tld_names-reversed-inc.cc) " +
-         "&& python3 $(location net/tools/dafsa/make_dafsa.py) " +
+         "&& " +
+         "python3 $(location net/tools/dafsa/make_dafsa.py) " +
          "--reverse " +
          "$(location net/base/registry_controlled_domains/effective_tld_names_unittest1.gperf) " +
          "$(location net/base/registry_controlled_domains/effective_tld_names_unittest1-reversed-inc.cc) " +
-         "&& python3 $(location net/tools/dafsa/make_dafsa.py) " +
+         "&& " +
+         "python3 $(location net/tools/dafsa/make_dafsa.py) " +
          "--reverse " +
          "$(location net/base/registry_controlled_domains/effective_tld_names_unittest2.gperf) " +
          "$(location net/base/registry_controlled_domains/effective_tld_names_unittest2-reversed-inc.cc) " +
-         "&& python3 $(location net/tools/dafsa/make_dafsa.py) " +
+         "&& " +
+         "python3 $(location net/tools/dafsa/make_dafsa.py) " +
          "--reverse " +
          "$(location net/base/registry_controlled_domains/effective_tld_names_unittest3.gperf) " +
          "$(location net/base/registry_controlled_domains/effective_tld_names_unittest3-reversed-inc.cc) " +
-         "&& python3 $(location net/tools/dafsa/make_dafsa.py) " +
+         "&& " +
+         "python3 $(location net/tools/dafsa/make_dafsa.py) " +
          "--reverse " +
          "$(location net/base/registry_controlled_domains/effective_tld_names_unittest4.gperf) " +
          "$(location net/base/registry_controlled_domains/effective_tld_names_unittest4-reversed-inc.cc) " +
-         "&& python3 $(location net/tools/dafsa/make_dafsa.py) " +
+         "&& " +
+         "python3 $(location net/tools/dafsa/make_dafsa.py) " +
          "--reverse " +
          "$(location net/base/registry_controlled_domains/effective_tld_names_unittest5.gperf) " +
          "$(location net/base/registry_controlled_domains/effective_tld_names_unittest5-reversed-inc.cc) " +
-         "&& python3 $(location net/tools/dafsa/make_dafsa.py) " +
+         "&& " +
+         "python3 $(location net/tools/dafsa/make_dafsa.py) " +
          "--reverse " +
          "$(location net/base/registry_controlled_domains/effective_tld_names_unittest6.gperf) " +
          "$(location net/base/registry_controlled_domains/effective_tld_names_unittest6-reversed-inc.cc)",
@@ -11093,6 +11282,52 @@
     ],
 }
 
+// GN: //net:cronet_buildflags
+cc_genrule {
+    name: "cronet_aml_net_cronet_buildflags",
+    cmd: "echo '--flags CRONET_BUILD=\"true\"' | " +
+         "$(location build/write_buildflag_header.py) --output " +
+         "$(out) " +
+         "--rulename " +
+         "//net:cronet_buildflags " +
+         "--gen-dir " +
+         ". " +
+         "--definitions " +
+         "/dev/stdin",
+    out: [
+        "net/base/cronet_buildflags.h",
+    ],
+    tool_files: [
+        "build/write_buildflag_header.py",
+    ],
+    apex_available: [
+        "com.android.tethering",
+    ],
+}
+
+// GN: //net:cronet_buildflags__testing
+cc_genrule {
+    name: "cronet_aml_net_cronet_buildflags__testing",
+    cmd: "echo '--flags CRONET_BUILD=\"true\"' | " +
+         "$(location build/write_buildflag_header.py) --output " +
+         "$(out) " +
+         "--rulename " +
+         "//net:cronet_buildflags " +
+         "--gen-dir " +
+         ". " +
+         "--definitions " +
+         "/dev/stdin",
+    out: [
+        "net/base/cronet_buildflags.h",
+    ],
+    tool_files: [
+        "build/write_buildflag_header.py",
+    ],
+    apex_available: [
+        "com.android.tethering",
+    ],
+}
+
 // GN: //net/dns:dns
 cc_object {
     name: "cronet_aml_net_dns_dns",
@@ -11136,6 +11371,8 @@
         "net/dns/test_dns_config_service.cc",
     ],
     shared_libs: [
+        "//external/cronet/third_party/boringssl:libcrypto",
+        "//external/cronet/third_party/boringssl:libssl",
         "libandroid",
         "liblog",
         "libz",
@@ -11150,7 +11387,6 @@
         "cronet_aml_net_preload_decoder",
         "cronet_aml_net_third_party_quiche_quiche",
         "cronet_aml_net_uri_template",
-        "cronet_aml_third_party_boringssl_boringssl",
         "cronet_aml_third_party_brotli_common",
         "cronet_aml_third_party_brotli_dec",
         "cronet_aml_third_party_icu_icui18n",
@@ -11417,6 +11653,8 @@
         "net/dns/public/util.cc",
     ],
     shared_libs: [
+        "//external/cronet/third_party/boringssl:libcrypto",
+        "//external/cronet/third_party/boringssl:libssl",
         "libandroid",
         "liblog",
         "libz",
@@ -11431,7 +11669,6 @@
         "cronet_aml_net_preload_decoder",
         "cronet_aml_net_third_party_quiche_quiche",
         "cronet_aml_net_uri_template",
-        "cronet_aml_third_party_boringssl_boringssl",
         "cronet_aml_third_party_brotli_common",
         "cronet_aml_third_party_brotli_dec",
         "cronet_aml_third_party_icu_icui18n",
@@ -11655,6 +11892,126 @@
     },
 }
 
+// GN: //net/dns/public:tests__testing
+cc_object {
+    name: "cronet_aml_net_dns_public_tests__testing",
+    srcs: [
+        "net/dns/public/dns_over_https_config_unittest.cc",
+        "net/dns/public/dns_over_https_server_config_unittest.cc",
+        "net/dns/public/doh_provider_entry_unittest.cc",
+    ],
+    shared_libs: [
+        "libandroid",
+        "liblog",
+        "libz",
+    ],
+    static_libs: [
+        "cronet_aml_base_allocator_partition_allocator_partition_alloc__testing",
+        "cronet_aml_base_base__testing",
+        "cronet_aml_base_base_static__testing",
+        "cronet_aml_base_third_party_double_conversion_double_conversion__testing",
+        "cronet_aml_base_third_party_dynamic_annotations_dynamic_annotations__testing",
+        "cronet_aml_crypto_crypto__testing",
+        "cronet_aml_net_net__testing",
+        "cronet_aml_net_preload_decoder__testing",
+        "cronet_aml_net_third_party_quiche_quiche__testing",
+        "cronet_aml_net_uri_template__testing",
+        "cronet_aml_testing_gtest_gtest__testing",
+        "cronet_aml_third_party_boringssl_boringssl__testing",
+        "cronet_aml_third_party_brotli_common__testing",
+        "cronet_aml_third_party_brotli_dec__testing",
+        "cronet_aml_third_party_icu_icui18n__testing",
+        "cronet_aml_third_party_icu_icuuc_private__testing",
+        "cronet_aml_third_party_libevent_libevent__testing",
+        "cronet_aml_third_party_modp_b64_modp_b64__testing",
+        "cronet_aml_third_party_protobuf_protobuf_lite__testing",
+        "cronet_aml_url_url__testing",
+    ],
+    generated_headers: [
+        "cronet_aml_build_chromeos_buildflags__testing",
+    ],
+    defaults: [
+        "cronet_aml_defaults",
+    ],
+    cflags: [
+        "-DANDROID",
+        "-DANDROID_NDK_VERSION_ROLL=r23_1",
+        "-DCR_CLANG_REVISION=\"llvmorg-16-init-6578-g0d30e92f-2\"",
+        "-DCR_LIBCXX_REVISION=64d36e572d3f9719c5d75011a718f33f11126851",
+        "-DDYNAMIC_ANNOTATIONS_ENABLED=0",
+        "-DGOOGLE_PROTOBUF_INTERNAL_DONATE_STEAL_INLINE=0",
+        "-DGOOGLE_PROTOBUF_NO_RTTI",
+        "-DGOOGLE_PROTOBUF_NO_STATIC_INITIALIZER",
+        "-DGTEST_API_=",
+        "-DGTEST_HAS_ABSL=1",
+        "-DGTEST_HAS_POSIX_RE=0",
+        "-DGTEST_HAS_TR1_TUPLE=0",
+        "-DGTEST_LANG_CXX11=1",
+        "-DHAVE_PTHREAD",
+        "-DHAVE_SYS_UIO_H",
+        "-DNDEBUG",
+        "-DNO_UNWIND_TABLES",
+        "-DNVALGRIND",
+        "-DOFFICIAL_BUILD",
+        "-DUNIT_TEST",
+        "-D_FORTIFY_SOURCE=2",
+        "-D_GNU_SOURCE",
+        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
+        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
+        "-D__STDC_CONSTANT_MACROS",
+        "-D__STDC_FORMAT_MACROS",
+        "-Oz",
+        "-fdata-sections",
+        "-ffunction-sections",
+        "-fno-asynchronous-unwind-tables",
+        "-fno-unwind-tables",
+        "-fvisibility-inlines-hidden",
+        "-fvisibility=hidden",
+        "-g1",
+    ],
+    local_include_dirs: [
+        "./",
+        "buildtools/third_party/libc++/",
+        "buildtools/third_party/libc++/trunk/include",
+        "buildtools/third_party/libc++abi/trunk/include",
+        "net/third_party/quiche/overrides/",
+        "net/third_party/quiche/src/",
+        "net/third_party/quiche/src/quiche/common/platform/default/",
+        "third_party/abseil-cpp/",
+        "third_party/boringssl/src/include/",
+        "third_party/googletest/custom/",
+        "third_party/googletest/src/googlemock/include/",
+        "third_party/googletest/src/googletest/include/",
+        "third_party/protobuf/src/",
+    ],
+    cpp_std: "c++17",
+    target: {
+        android_arm: {
+            cflags: [
+                "-fstack-protector",
+            ],
+        },
+        android_arm64: {
+            cflags: [
+                "-fstack-protector",
+                "-mno-outline",
+                "-mno-outline-atomics",
+            ],
+        },
+        android_x86: {
+            cflags: [
+                "-msse3",
+            ],
+        },
+        android_x86_64: {
+            cflags: [
+                "-fstack-protector",
+                "-msse3",
+            ],
+        },
+    },
+}
+
 // GN: //net/dns:test_support__testing
 cc_object {
     name: "cronet_aml_net_dns_test_support__testing",
@@ -11775,6 +12132,171 @@
     },
 }
 
+// GN: //net/dns:tests__testing
+cc_object {
+    name: "cronet_aml_net_dns_tests__testing",
+    srcs: [
+        "net/dns/address_info_unittest.cc",
+        "net/dns/address_sorter_posix_unittest.cc",
+        "net/dns/address_sorter_unittest.cc",
+        "net/dns/context_host_resolver_unittest.cc",
+        "net/dns/dns_alias_utility_unittest.cc",
+        "net/dns/dns_client_unittest.cc",
+        "net/dns/dns_config_service_android_unittest.cc",
+        "net/dns/dns_config_service_unittest.cc",
+        "net/dns/dns_hosts_unittest.cc",
+        "net/dns/dns_query_unittest.cc",
+        "net/dns/dns_response_result_extractor_unittest.cc",
+        "net/dns/dns_response_unittest.cc",
+        "net/dns/dns_transaction_unittest.cc",
+        "net/dns/dns_udp_tracker_unittest.cc",
+        "net/dns/dns_util_unittest.cc",
+        "net/dns/host_cache_unittest.cc",
+        "net/dns/host_resolver_manager_unittest.cc",
+        "net/dns/https_record_rdata_unittest.cc",
+        "net/dns/httpssvc_metrics_unittest.cc",
+        "net/dns/mapped_host_resolver_unittest.cc",
+        "net/dns/nsswitch_reader_unittest.cc",
+        "net/dns/opt_record_rdata_unittest.cc",
+        "net/dns/record_parsed_unittest.cc",
+        "net/dns/record_rdata_unittest.cc",
+        "net/dns/resolve_context_unittest.cc",
+        "net/dns/serial_worker_unittest.cc",
+        "net/dns/system_dns_config_change_notifier_unittest.cc",
+    ],
+    shared_libs: [
+        "libandroid",
+        "liblog",
+        "libz",
+    ],
+    static_libs: [
+        "cronet_aml_base_allocator_partition_allocator_partition_alloc__testing",
+        "cronet_aml_base_base__testing",
+        "cronet_aml_base_base_static__testing",
+        "cronet_aml_base_i18n__testing",
+        "cronet_aml_base_test_test_config__testing",
+        "cronet_aml_base_test_test_support__testing",
+        "cronet_aml_base_third_party_double_conversion_double_conversion__testing",
+        "cronet_aml_base_third_party_dynamic_annotations_dynamic_annotations__testing",
+        "cronet_aml_crypto_crypto__testing",
+        "cronet_aml_net_gtest_util__testing",
+        "cronet_aml_net_net__testing",
+        "cronet_aml_net_preload_decoder__testing",
+        "cronet_aml_net_test_support__testing",
+        "cronet_aml_net_third_party_quiche_quiche__testing",
+        "cronet_aml_net_third_party_quiche_quiche_tool_support__testing",
+        "cronet_aml_net_uri_template__testing",
+        "cronet_aml_testing_gtest_gtest__testing",
+        "cronet_aml_third_party_boringssl_boringssl__testing",
+        "cronet_aml_third_party_brotli_common__testing",
+        "cronet_aml_third_party_brotli_dec__testing",
+        "cronet_aml_third_party_ced_ced__testing",
+        "cronet_aml_third_party_icu_icui18n__testing",
+        "cronet_aml_third_party_icu_icuuc_private__testing",
+        "cronet_aml_third_party_libevent_libevent__testing",
+        "cronet_aml_third_party_libxml_libxml__testing",
+        "cronet_aml_third_party_libxml_libxml_utils__testing",
+        "cronet_aml_third_party_libxml_xml_reader__testing",
+        "cronet_aml_third_party_modp_b64_modp_b64__testing",
+        "cronet_aml_third_party_protobuf_protobuf_lite__testing",
+        "cronet_aml_url_url__testing",
+    ],
+    generated_headers: [
+        "cronet_aml_build_chromeos_buildflags__testing",
+    ],
+    defaults: [
+        "cronet_aml_defaults",
+    ],
+    cflags: [
+        "-DANDROID",
+        "-DANDROID_NDK_VERSION_ROLL=r23_1",
+        "-DCR_CLANG_REVISION=\"llvmorg-16-init-6578-g0d30e92f-2\"",
+        "-DCR_LIBCXX_REVISION=64d36e572d3f9719c5d75011a718f33f11126851",
+        "-DDYNAMIC_ANNOTATIONS_ENABLED=0",
+        "-DGOOGLE_PROTOBUF_INTERNAL_DONATE_STEAL_INLINE=0",
+        "-DGOOGLE_PROTOBUF_NO_RTTI",
+        "-DGOOGLE_PROTOBUF_NO_STATIC_INITIALIZER",
+        "-DGTEST_API_=",
+        "-DGTEST_HAS_ABSL=1",
+        "-DGTEST_HAS_POSIX_RE=0",
+        "-DGTEST_HAS_TR1_TUPLE=0",
+        "-DGTEST_LANG_CXX11=1",
+        "-DHAVE_PTHREAD",
+        "-DHAVE_SYS_UIO_H",
+        "-DICU_UTIL_DATA_IMPL=ICU_UTIL_DATA_FILE",
+        "-DNDEBUG",
+        "-DNO_UNWIND_TABLES",
+        "-DNVALGRIND",
+        "-DOFFICIAL_BUILD",
+        "-DUNIT_TEST",
+        "-DUSE_CHROMIUM_ICU=1",
+        "-DUSE_REMOTE_TEST_SERVER",
+        "-DU_ENABLE_DYLOAD=0",
+        "-DU_ENABLE_RESOURCE_TRACING=0",
+        "-DU_ENABLE_TRACING=1",
+        "-DU_STATIC_IMPLEMENTATION",
+        "-DU_USING_ICU_NAMESPACE=0",
+        "-D_FORTIFY_SOURCE=2",
+        "-D_GNU_SOURCE",
+        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
+        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
+        "-D__STDC_CONSTANT_MACROS",
+        "-D__STDC_FORMAT_MACROS",
+        "-Oz",
+        "-fdata-sections",
+        "-ffunction-sections",
+        "-fno-asynchronous-unwind-tables",
+        "-fno-unwind-tables",
+        "-fvisibility-inlines-hidden",
+        "-fvisibility=hidden",
+        "-g1",
+    ],
+    local_include_dirs: [
+        "./",
+        "buildtools/third_party/libc++/",
+        "buildtools/third_party/libc++/trunk/include",
+        "buildtools/third_party/libc++abi/trunk/include",
+        "net/third_party/quiche/overrides/",
+        "net/third_party/quiche/src/",
+        "net/third_party/quiche/src/quiche/common/platform/default/",
+        "third_party/abseil-cpp/",
+        "third_party/boringssl/src/include/",
+        "third_party/ced/src/",
+        "third_party/googletest/custom/",
+        "third_party/googletest/src/googlemock/include/",
+        "third_party/googletest/src/googletest/include/",
+        "third_party/icu/source/common/",
+        "third_party/icu/source/i18n/",
+        "third_party/protobuf/src/",
+    ],
+    cpp_std: "c++17",
+    target: {
+        android_arm: {
+            cflags: [
+                "-fstack-protector",
+            ],
+        },
+        android_arm64: {
+            cflags: [
+                "-fstack-protector",
+                "-mno-outline",
+                "-mno-outline-atomics",
+            ],
+        },
+        android_x86: {
+            cflags: [
+                "-msse3",
+            ],
+        },
+        android_x86_64: {
+            cflags: [
+                "-fstack-protector",
+                "-msse3",
+            ],
+        },
+    },
+}
+
 // GN: //net:effective_connection_type_java
 java_genrule {
     name: "cronet_aml_net_effective_connection_type_java",
@@ -12009,6 +12531,8 @@
         "net/http/transport_security_state.cc",
     ],
     shared_libs: [
+        "//external/cronet/third_party/boringssl:libcrypto",
+        "//external/cronet/third_party/boringssl:libssl",
         "libandroid",
         "liblog",
         "libz",
@@ -12023,7 +12547,6 @@
         "cronet_aml_net_preload_decoder",
         "cronet_aml_net_third_party_quiche_quiche",
         "cronet_aml_net_uri_template",
-        "cronet_aml_third_party_boringssl_boringssl",
         "cronet_aml_third_party_brotli_common",
         "cronet_aml_third_party_brotli_dec",
         "cronet_aml_third_party_icu_icui18n",
@@ -12242,6 +12765,47 @@
     },
 }
 
+// GN: //net/http:transport_security_state_unittest_data__testing
+cc_genrule {
+    name: "cronet_aml_net_http_transport_security_state_unittest_data__testing",
+    tools: [
+        "cronet_aml_net_tools_transport_security_state_generator_transport_security_state_generator__testing",
+    ],
+    cmd: "$(location cronet_aml_net_tools_transport_security_state_generator_transport_security_state_generator__testing) " +
+         "$(location net/http/transport_security_state_static_unittest1.json) " +
+         "$(location net/http/transport_security_state_static_unittest.pins) " +
+         "$(location net/http/transport_security_state_static_unittest.template) " +
+         "$(location net/http/transport_security_state_static_unittest1.h) " +
+         "&& " +
+         "$(location cronet_aml_net_tools_transport_security_state_generator_transport_security_state_generator__testing) " +
+         "$(location net/http/transport_security_state_static_unittest2.json) " +
+         "$(location net/http/transport_security_state_static_unittest.pins) " +
+         "$(location net/http/transport_security_state_static_unittest.template) " +
+         "$(location net/http/transport_security_state_static_unittest2.h) " +
+         "&& " +
+         "$(location cronet_aml_net_tools_transport_security_state_generator_transport_security_state_generator__testing) " +
+         "$(location net/http/transport_security_state_static_unittest3.json) " +
+         "$(location net/http/transport_security_state_static_unittest.pins) " +
+         "$(location net/http/transport_security_state_static_unittest.template) " +
+         "$(location net/http/transport_security_state_static_unittest3.h)",
+    out: [
+        "net/http/transport_security_state_static_unittest1.h",
+        "net/http/transport_security_state_static_unittest2.h",
+        "net/http/transport_security_state_static_unittest3.h",
+    ],
+    tool_files: [
+        "build/gn_run_binary.py",
+        "net/http/transport_security_state_static_unittest.pins",
+        "net/http/transport_security_state_static_unittest.template",
+        "net/http/transport_security_state_static_unittest1.json",
+        "net/http/transport_security_state_static_unittest2.json",
+        "net/http/transport_security_state_static_unittest3.json",
+    ],
+    apex_available: [
+        "com.android.tethering",
+    ],
+}
+
 // GN: //net/http:transport_security_state_unittest_data_default__testing
 cc_genrule {
     name: "cronet_aml_net_http_transport_security_state_unittest_data_default__testing",
@@ -12267,52 +12831,6 @@
     ],
 }
 
-// GN: //net:ios_cronet_buildflags
-cc_genrule {
-    name: "cronet_aml_net_ios_cronet_buildflags",
-    cmd: "echo '--flags CRONET_BUILD=\"true\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//net:ios_cronet_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "net/socket/ios_cronet_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //net:ios_cronet_buildflags__testing
-cc_genrule {
-    name: "cronet_aml_net_ios_cronet_buildflags__testing",
-    cmd: "echo '--flags CRONET_BUILD=\"true\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//net:ios_cronet_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "net/socket/ios_cronet_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
 // GN: //net:isolation_info_proto__testing
 cc_genrule {
     name: "cronet_aml_net_isolation_info_proto__testing_gen",
@@ -12889,6 +13407,8 @@
         "net/url_request/websocket_handshake_userdata_key.cc",
     ],
     shared_libs: [
+        "//external/cronet/third_party/boringssl:libcrypto",
+        "//external/cronet/third_party/boringssl:libssl",
         "libandroid",
         "liblog",
         "libz",
@@ -12903,7 +13423,6 @@
         "cronet_aml_net_preload_decoder",
         "cronet_aml_net_third_party_quiche_quiche",
         "cronet_aml_net_uri_template",
-        "cronet_aml_third_party_boringssl_boringssl",
         "cronet_aml_third_party_brotli_common",
         "cronet_aml_third_party_brotli_dec",
         "cronet_aml_third_party_icu_icui18n",
@@ -12920,7 +13439,7 @@
         "cronet_aml_build_chromeos_buildflags",
         "cronet_aml_net_base_registry_controlled_domains_registry_controlled_domains",
         "cronet_aml_net_buildflags",
-        "cronet_aml_net_ios_cronet_buildflags",
+        "cronet_aml_net_cronet_buildflags",
         "cronet_aml_net_isolation_info_proto_gen_headers",
         "cronet_aml_net_net_jni_headers",
         "cronet_aml_net_net_nqe_proto_gen_headers",
@@ -12934,7 +13453,7 @@
         "cronet_aml_build_chromeos_buildflags",
         "cronet_aml_net_base_registry_controlled_domains_registry_controlled_domains",
         "cronet_aml_net_buildflags",
-        "cronet_aml_net_ios_cronet_buildflags",
+        "cronet_aml_net_cronet_buildflags",
         "cronet_aml_net_isolation_info_proto_gen_headers",
         "cronet_aml_net_net_jni_headers",
         "cronet_aml_net_net_nqe_proto_gen_headers",
@@ -13579,7 +14098,7 @@
         "cronet_aml_build_chromeos_buildflags__testing",
         "cronet_aml_net_base_registry_controlled_domains_registry_controlled_domains__testing",
         "cronet_aml_net_buildflags__testing",
-        "cronet_aml_net_ios_cronet_buildflags__testing",
+        "cronet_aml_net_cronet_buildflags__testing",
         "cronet_aml_net_isolation_info_proto__testing_gen_headers",
         "cronet_aml_net_net_jni_headers__testing",
         "cronet_aml_net_net_nqe_proto__testing_gen_headers",
@@ -13593,7 +14112,7 @@
         "cronet_aml_build_chromeos_buildflags__testing",
         "cronet_aml_net_base_registry_controlled_domains_registry_controlled_domains__testing",
         "cronet_aml_net_buildflags__testing",
-        "cronet_aml_net_ios_cronet_buildflags__testing",
+        "cronet_aml_net_cronet_buildflags__testing",
         "cronet_aml_net_isolation_info_proto__testing_gen_headers",
         "cronet_aml_net_net_jni_headers__testing",
         "cronet_aml_net_net_nqe_proto__testing_gen_headers",
@@ -13716,6 +14235,8 @@
         ":cronet_aml_net_isolation_info_proto_gen",
     ],
     shared_libs: [
+        "//external/cronet/third_party/boringssl:libcrypto",
+        "//external/cronet/third_party/boringssl:libssl",
         "libandroid",
         "liblog",
         "libprotobuf-cpp-lite",
@@ -13728,7 +14249,6 @@
         "cronet_aml_base_third_party_double_conversion_double_conversion",
         "cronet_aml_base_third_party_dynamic_annotations_dynamic_annotations",
         "cronet_aml_net_preload_decoder",
-        "cronet_aml_third_party_boringssl_boringssl",
         "cronet_aml_third_party_brotli_common",
         "cronet_aml_third_party_brotli_dec",
         "cronet_aml_third_party_icu_icui18n",
@@ -14229,6 +14749,8 @@
         ":cronet_aml_net_third_party_quiche_net_quic_test_tools_proto_gen",
     ],
     shared_libs: [
+        "//external/cronet/third_party/boringssl:libcrypto",
+        "//external/cronet/third_party/boringssl:libssl",
         "libandroid",
         "liblog",
         "libprotobuf-cpp-lite",
@@ -14243,7 +14765,6 @@
         "cronet_aml_crypto_crypto",
         "cronet_aml_net_third_party_quiche_quiche",
         "cronet_aml_net_uri_template",
-        "cronet_aml_third_party_boringssl_boringssl",
         "cronet_aml_third_party_icu_icui18n",
         "cronet_aml_third_party_icu_icuuc_private",
         "cronet_aml_third_party_libevent_libevent",
@@ -14439,6 +14960,649 @@
     },
 }
 
+// GN: //net:net_test_jni_headers__testing
+cc_genrule {
+    name: "cronet_aml_net_net_test_jni_headers__testing",
+    srcs: [
+        "net/android/javatests/src/org/chromium/net/AndroidKeyStoreTestUtil.java",
+        "net/android/javatests/src/org/chromium/net/AndroidProxyConfigServiceTestUtil.java",
+        "net/test/android/javatests/src/org/chromium/net/AndroidNetworkLibraryTestUtil.java",
+        "net/test/android/javatests/src/org/chromium/net/test/DummySpnegoAuthenticator.java",
+        "net/test/android/javatests/src/org/chromium/net/test/EmbeddedTestServerImpl.java",
+    ],
+    cmd: "$(location base/android/jni_generator/jni_generator.py) --ptr_type " +
+         "long " +
+         "--output_dir " +
+         "$(genDir)/net/net_test_jni_headers " +
+         "--includes " +
+         "base/android/jni_generator/jni_generator_helper.h " +
+         "--use_proxy_hash " +
+         "--output_name " +
+         "AndroidKeyStoreTestUtil_jni.h " +
+         "--output_name " +
+         "AndroidProxyConfigServiceTestUtil_jni.h " +
+         "--output_name " +
+         "AndroidNetworkLibraryTestUtil_jni.h " +
+         "--output_name " +
+         "DummySpnegoAuthenticator_jni.h " +
+         "--output_name " +
+         "EmbeddedTestServerImpl_jni.h " +
+         "--input_file " +
+         "$(location net/android/javatests/src/org/chromium/net/AndroidKeyStoreTestUtil.java) " +
+         "--input_file " +
+         "$(location net/android/javatests/src/org/chromium/net/AndroidProxyConfigServiceTestUtil.java) " +
+         "--input_file " +
+         "$(location net/test/android/javatests/src/org/chromium/net/AndroidNetworkLibraryTestUtil.java) " +
+         "--input_file " +
+         "$(location net/test/android/javatests/src/org/chromium/net/test/DummySpnegoAuthenticator.java) " +
+         "--input_file " +
+         "$(location net/test/android/javatests/src/org/chromium/net/test/EmbeddedTestServerImpl.java)",
+    out: [
+        "net/net_test_jni_headers/AndroidKeyStoreTestUtil_jni.h",
+        "net/net_test_jni_headers/AndroidNetworkLibraryTestUtil_jni.h",
+        "net/net_test_jni_headers/AndroidProxyConfigServiceTestUtil_jni.h",
+        "net/net_test_jni_headers/DummySpnegoAuthenticator_jni.h",
+        "net/net_test_jni_headers/EmbeddedTestServerImpl_jni.h",
+    ],
+    tool_files: [
+        "base/android/jni_generator/android_jar.classes",
+        "base/android/jni_generator/jni_generator.py",
+        "build/android/gyp/util/__init__.py",
+        "build/android/gyp/util/build_utils.py",
+        "build/gn_helpers.py",
+    ],
+    apex_available: [
+        "com.android.tethering",
+    ],
+}
+
+// GN: //net:net_unittests__library__testing
+cc_library_shared {
+    name: "cronet_aml_net_net_unittests__library__testing",
+    srcs: [
+        ":cronet_aml_buildtools_third_party_libc___libc____testing",
+        ":cronet_aml_buildtools_third_party_libc__abi_libc__abi__testing",
+        ":cronet_aml_net_dns_public_tests__testing",
+        ":cronet_aml_net_dns_tests__testing",
+        ":cronet_aml_net_quic_test_flags_utils__testing",
+        ":cronet_aml_net_quic_test_tools__testing",
+        ":cronet_aml_net_simple_quic_tools__testing",
+        ":cronet_aml_net_spdy_test_tools__testing",
+        ":cronet_aml_net_third_party_quiche_quiche_tests__testing",
+        ":cronet_aml_net_tools_huffman_trie_huffman_trie_generator_sources__testing",
+        ":cronet_aml_testing_android_native_test_native_test_native_code__testing",
+        ":cronet_aml_testing_android_native_test_native_test_support__testing",
+        ":cronet_aml_third_party_abseil_cpp_absl_base_base__testing",
+        ":cronet_aml_third_party_abseil_cpp_absl_base_log_severity__testing",
+        ":cronet_aml_third_party_abseil_cpp_absl_base_malloc_internal__testing",
+        ":cronet_aml_third_party_abseil_cpp_absl_base_raw_logging_internal__testing",
+        ":cronet_aml_third_party_abseil_cpp_absl_base_spinlock_wait__testing",
+        ":cronet_aml_third_party_abseil_cpp_absl_base_strerror__testing",
+        ":cronet_aml_third_party_abseil_cpp_absl_base_throw_delegate__testing",
+        ":cronet_aml_third_party_abseil_cpp_absl_container_hashtablez_sampler__testing",
+        ":cronet_aml_third_party_abseil_cpp_absl_container_raw_hash_set__testing",
+        ":cronet_aml_third_party_abseil_cpp_absl_debugging_debugging_internal__testing",
+        ":cronet_aml_third_party_abseil_cpp_absl_debugging_demangle_internal__testing",
+        ":cronet_aml_third_party_abseil_cpp_absl_debugging_examine_stack__testing",
+        ":cronet_aml_third_party_abseil_cpp_absl_debugging_failure_signal_handler__testing",
+        ":cronet_aml_third_party_abseil_cpp_absl_debugging_stacktrace__testing",
+        ":cronet_aml_third_party_abseil_cpp_absl_debugging_symbolize__testing",
+        ":cronet_aml_third_party_abseil_cpp_absl_hash_city__testing",
+        ":cronet_aml_third_party_abseil_cpp_absl_hash_hash__testing",
+        ":cronet_aml_third_party_abseil_cpp_absl_hash_low_level_hash__testing",
+        ":cronet_aml_third_party_abseil_cpp_absl_numeric_int128__testing",
+        ":cronet_aml_third_party_abseil_cpp_absl_profiling_exponential_biased__testing",
+        ":cronet_aml_third_party_abseil_cpp_absl_random_distributions__testing",
+        ":cronet_aml_third_party_abseil_cpp_absl_random_internal_platform__testing",
+        ":cronet_aml_third_party_abseil_cpp_absl_random_internal_pool_urbg__testing",
+        ":cronet_aml_third_party_abseil_cpp_absl_random_internal_randen__testing",
+        ":cronet_aml_third_party_abseil_cpp_absl_random_internal_randen_hwaes__testing",
+        ":cronet_aml_third_party_abseil_cpp_absl_random_internal_randen_hwaes_impl__testing",
+        ":cronet_aml_third_party_abseil_cpp_absl_random_internal_randen_slow__testing",
+        ":cronet_aml_third_party_abseil_cpp_absl_random_internal_seed_material__testing",
+        ":cronet_aml_third_party_abseil_cpp_absl_random_seed_gen_exception__testing",
+        ":cronet_aml_third_party_abseil_cpp_absl_random_seed_sequences__testing",
+        ":cronet_aml_third_party_abseil_cpp_absl_status_status__testing",
+        ":cronet_aml_third_party_abseil_cpp_absl_status_statusor__testing",
+        ":cronet_aml_third_party_abseil_cpp_absl_strings_cord__testing",
+        ":cronet_aml_third_party_abseil_cpp_absl_strings_cord_internal__testing",
+        ":cronet_aml_third_party_abseil_cpp_absl_strings_cordz_functions__testing",
+        ":cronet_aml_third_party_abseil_cpp_absl_strings_cordz_handle__testing",
+        ":cronet_aml_third_party_abseil_cpp_absl_strings_cordz_info__testing",
+        ":cronet_aml_third_party_abseil_cpp_absl_strings_internal__testing",
+        ":cronet_aml_third_party_abseil_cpp_absl_strings_str_format_internal__testing",
+        ":cronet_aml_third_party_abseil_cpp_absl_strings_strings__testing",
+        ":cronet_aml_third_party_abseil_cpp_absl_synchronization_graphcycles_internal__testing",
+        ":cronet_aml_third_party_abseil_cpp_absl_synchronization_synchronization__testing",
+        ":cronet_aml_third_party_abseil_cpp_absl_time_internal_cctz_civil_time__testing",
+        ":cronet_aml_third_party_abseil_cpp_absl_time_internal_cctz_time_zone__testing",
+        ":cronet_aml_third_party_abseil_cpp_absl_time_time__testing",
+        ":cronet_aml_third_party_abseil_cpp_absl_types_bad_optional_access__testing",
+        ":cronet_aml_third_party_abseil_cpp_absl_types_bad_variant_access__testing",
+        ":cronet_aml_third_party_googletest_gmock__testing",
+        ":cronet_aml_third_party_googletest_gtest__testing",
+        "net/android/dummy_spnego_authenticator.cc",
+        "net/android/http_auth_negotiate_android_unittest.cc",
+        "net/android/network_change_notifier_android_unittest.cc",
+        "net/android/network_library_unittest.cc",
+        "net/android/traffic_stats_unittest.cc",
+        "net/base/address_family_unittest.cc",
+        "net/base/address_list_unittest.cc",
+        "net/base/address_tracker_linux_unittest.cc",
+        "net/base/backoff_entry_serializer_unittest.cc",
+        "net/base/backoff_entry_unittest.cc",
+        "net/base/chunked_upload_data_stream_unittest.cc",
+        "net/base/data_url_unittest.cc",
+        "net/base/datagram_buffer_unittest.cc",
+        "net/base/elements_upload_data_stream_unittest.cc",
+        "net/base/expiring_cache_unittest.cc",
+        "net/base/file_stream_unittest.cc",
+        "net/base/host_mapping_rules_unittest.cc",
+        "net/base/host_port_pair_unittest.cc",
+        "net/base/interval_test.cc",
+        "net/base/ip_address_unittest.cc",
+        "net/base/ip_endpoint_unittest.cc",
+        "net/base/isolation_info_unittest.cc",
+        "net/base/lookup_string_in_fixed_set_unittest.cc",
+        "net/base/mime_sniffer_unittest.cc",
+        "net/base/mime_util_unittest.cc",
+        "net/base/net_errors_unittest.cc",
+        "net/base/net_string_util_unittest.cc",
+        "net/base/network_activity_monitor_unittest.cc",
+        "net/base/network_anonymization_key_unittest.cc",
+        "net/base/network_change_notifier_posix_unittest.cc",
+        "net/base/network_change_notifier_unittest.cc",
+        "net/base/network_delegate_unittest.cc",
+        "net/base/network_interfaces_getifaddrs_unittest.cc",
+        "net/base/network_interfaces_linux_unittest.cc",
+        "net/base/network_interfaces_unittest.cc",
+        "net/base/network_isolation_key_unittest.cc",
+        "net/base/parse_number_unittest.cc",
+        "net/base/port_util_unittest.cc",
+        "net/base/prioritized_dispatcher_unittest.cc",
+        "net/base/prioritized_task_runner_unittest.cc",
+        "net/base/priority_queue_unittest.cc",
+        "net/base/proxy_server_unittest.cc",
+        "net/base/proxy_string_util_unittest.cc",
+        "net/base/registry_controlled_domains/registry_controlled_domain_unittest.cc",
+        "net/base/scheme_host_port_matcher_rule_unittest.cc",
+        "net/base/scheme_host_port_matcher_unittest.cc",
+        "net/base/schemeful_site_unittest.cc",
+        "net/base/sockaddr_util_posix_unittest.cc",
+        "net/base/test_completion_callback_unittest.cc",
+        "net/base/test_proxy_delegate.cc",
+        "net/base/upload_bytes_element_reader_unittest.cc",
+        "net/base/upload_file_element_reader_unittest.cc",
+        "net/cert/caching_cert_verifier_unittest.cc",
+        "net/cert/cert_and_ct_verifier_unittest.cc",
+        "net/cert/cert_verifier_unittest.cc",
+        "net/cert/cert_verify_proc_android_unittest.cc",
+        "net/cert/cert_verify_proc_builtin_unittest.cc",
+        "net/cert/cert_verify_proc_unittest.cc",
+        "net/cert/coalescing_cert_verifier_unittest.cc",
+        "net/cert/crl_set_unittest.cc",
+        "net/cert/ct_log_response_parser_unittest.cc",
+        "net/cert/ct_log_verifier_unittest.cc",
+        "net/cert/ct_objects_extractor_unittest.cc",
+        "net/cert/ct_serialization_unittest.cc",
+        "net/cert/ev_root_ca_metadata_unittest.cc",
+        "net/cert/internal/cert_issuer_source_aia_unittest.cc",
+        "net/cert/internal/cert_issuer_source_sync_unittest.cc",
+        "net/cert/internal/crl_unittest.cc",
+        "net/cert/internal/general_names_unittest.cc",
+        "net/cert/internal/revocation_checker_unittest.cc",
+        "net/cert/internal/revocation_util_unittest.cc",
+        "net/cert/internal/system_trust_store_unittest.cc",
+        "net/cert/known_roots_unittest.cc",
+        "net/cert/merkle_audit_proof_unittest.cc",
+        "net/cert/merkle_tree_leaf_unittest.cc",
+        "net/cert/multi_log_ct_verifier_unittest.cc",
+        "net/cert/multi_threaded_cert_verifier_unittest.cc",
+        "net/cert/pem_unittest.cc",
+        "net/cert/pki/cert_issuer_source_static_unittest.cc",
+        "net/cert/pki/certificate_policies_unittest.cc",
+        "net/cert/pki/extended_key_usage_unittest.cc",
+        "net/cert/pki/name_constraints_unittest.cc",
+        "net/cert/pki/nist_pkits_unittest.cc",
+        "net/cert/pki/ocsp_unittest.cc",
+        "net/cert/pki/parse_certificate_unittest.cc",
+        "net/cert/pki/parse_name_unittest.cc",
+        "net/cert/pki/parsed_certificate_unittest.cc",
+        "net/cert/pki/path_builder_pkits_unittest.cc",
+        "net/cert/pki/path_builder_unittest.cc",
+        "net/cert/pki/path_builder_verify_certificate_chain_unittest.cc",
+        "net/cert/pki/signature_algorithm_unittest.cc",
+        "net/cert/pki/simple_path_builder_delegate_unittest.cc",
+        "net/cert/pki/string_util_unittest.cc",
+        "net/cert/pki/test_helpers.cc",
+        "net/cert/pki/trust_store_collection_unittest.cc",
+        "net/cert/pki/verify_certificate_chain_pkits_unittest.cc",
+        "net/cert/pki/verify_certificate_chain_unittest.cc",
+        "net/cert/pki/verify_name_match_unittest.cc",
+        "net/cert/pki/verify_signed_data_unittest.cc",
+        "net/cert/signed_certificate_timestamp_unittest.cc",
+        "net/cert/symantec_certs_unittest.cc",
+        "net/cert/test_root_certs_unittest.cc",
+        "net/cert/x509_cert_types_unittest.cc",
+        "net/cert/x509_util_unittest.cc",
+        "net/cert_net/cert_net_fetcher_url_request_unittest.cc",
+        "net/cookies/canonical_cookie_unittest.cc",
+        "net/cookies/cookie_constants_unittest.cc",
+        "net/cookies/cookie_deletion_info_unittest.cc",
+        "net/cookies/cookie_inclusion_status_unittest.cc",
+        "net/cookies/cookie_monster_unittest.cc",
+        "net/cookies/cookie_options_unittest.cc",
+        "net/cookies/cookie_partition_key_collection_unittest.cc",
+        "net/cookies/cookie_partition_key_unittest.cc",
+        "net/cookies/cookie_util_unittest.cc",
+        "net/cookies/parsed_cookie_unittest.cc",
+        "net/cookies/site_for_cookies_unittest.cc",
+        "net/cookies/static_cookie_policy_unittest.cc",
+        "net/der/encode_values_unittest.cc",
+        "net/der/input_unittest.cc",
+        "net/der/parse_values_unittest.cc",
+        "net/der/parser_unittest.cc",
+        "net/disk_cache/backend_cleanup_tracker_unittest.cc",
+        "net/disk_cache/backend_unittest.cc",
+        "net/disk_cache/blockfile/addr_unittest.cc",
+        "net/disk_cache/blockfile/bitmap_unittest.cc",
+        "net/disk_cache/blockfile/block_files_unittest.cc",
+        "net/disk_cache/blockfile/mapped_file_unittest.cc",
+        "net/disk_cache/blockfile/stats_unittest.cc",
+        "net/disk_cache/blockfile/storage_block_unittest.cc",
+        "net/disk_cache/cache_util_unittest.cc",
+        "net/disk_cache/entry_unittest.cc",
+        "net/disk_cache/simple/simple_file_enumerator_unittest.cc",
+        "net/disk_cache/simple/simple_file_tracker_unittest.cc",
+        "net/disk_cache/simple/simple_index_file_unittest.cc",
+        "net/disk_cache/simple/simple_index_unittest.cc",
+        "net/disk_cache/simple/simple_test_util.cc",
+        "net/disk_cache/simple/simple_util_unittest.cc",
+        "net/disk_cache/simple/simple_version_upgrade_unittest.cc",
+        "net/filter/brotli_source_stream_unittest.cc",
+        "net/filter/filter_source_stream_unittest.cc",
+        "net/filter/gzip_source_stream_unittest.cc",
+        "net/first_party_sets/addition_overlaps_union_find_unittest.cc",
+        "net/first_party_sets/first_party_sets_cache_filter_unittest.cc",
+        "net/first_party_sets/first_party_sets_context_config_unittest.cc",
+        "net/first_party_sets/global_first_party_sets_unittest.cc",
+        "net/http/alternative_service_unittest.cc",
+        "net/http/bidirectional_stream_unittest.cc",
+        "net/http/broken_alternative_services_unittest.cc",
+        "net/http/http_auth_cache_unittest.cc",
+        "net/http/http_auth_challenge_tokenizer_unittest.cc",
+        "net/http/http_auth_controller_unittest.cc",
+        "net/http/http_auth_filter_unittest.cc",
+        "net/http/http_auth_handler_basic_unittest.cc",
+        "net/http/http_auth_handler_digest_unittest.cc",
+        "net/http/http_auth_handler_factory_unittest.cc",
+        "net/http/http_auth_handler_mock.cc",
+        "net/http/http_auth_handler_negotiate_unittest.cc",
+        "net/http/http_auth_handler_ntlm_portable_unittest.cc",
+        "net/http/http_auth_handler_unittest.cc",
+        "net/http/http_auth_multi_round_parse_unittest.cc",
+        "net/http/http_auth_preferences_unittest.cc",
+        "net/http/http_auth_unittest.cc",
+        "net/http/http_basic_state_unittest.cc",
+        "net/http/http_byte_range_unittest.cc",
+        "net/http/http_cache_lookup_manager_unittest.cc",
+        "net/http/http_cache_unittest.cc",
+        "net/http/http_cache_writers_unittest.cc",
+        "net/http/http_chunked_decoder_unittest.cc",
+        "net/http/http_content_disposition_unittest.cc",
+        "net/http/http_log_util_unittest.cc",
+        "net/http/http_network_layer_unittest.cc",
+        "net/http/http_network_transaction_unittest.cc",
+        "net/http/http_proxy_client_socket_unittest.cc",
+        "net/http/http_proxy_connect_job_unittest.cc",
+        "net/http/http_request_headers_unittest.cc",
+        "net/http/http_request_info_unittest.cc",
+        "net/http/http_response_body_drainer_unittest.cc",
+        "net/http/http_response_headers_unittest.cc",
+        "net/http/http_response_info_unittest.cc",
+        "net/http/http_security_headers_unittest.cc",
+        "net/http/http_server_properties_manager_unittest.cc",
+        "net/http/http_server_properties_unittest.cc",
+        "net/http/http_status_code_unittest.cc",
+        "net/http/http_stream_factory_job_controller_unittest.cc",
+        "net/http/http_stream_factory_unittest.cc",
+        "net/http/http_stream_parser_unittest.cc",
+        "net/http/http_stream_request_unittest.cc",
+        "net/http/http_util_unittest.cc",
+        "net/http/http_vary_data_unittest.cc",
+        "net/http/mock_allow_http_auth_preferences.cc",
+        "net/http/test_upload_data_stream_not_allow_http1.cc",
+        "net/http/transport_security_persister_unittest.cc",
+        "net/http/transport_security_state_unittest.cc",
+        "net/http/url_security_manager_unittest.cc",
+        "net/http/webfonts_histogram_unittest.cc",
+        "net/log/file_net_log_observer_unittest.cc",
+        "net/log/net_log_capture_mode_unittest.cc",
+        "net/log/net_log_unittest.cc",
+        "net/log/net_log_util_unittest.cc",
+        "net/log/net_log_values_unittest.cc",
+        "net/network_error_logging/mock_persistent_nel_store_unittest.cc",
+        "net/network_error_logging/network_error_logging_service_unittest.cc",
+        "net/nqe/effective_connection_type_unittest.cc",
+        "net/nqe/event_creator_unittest.cc",
+        "net/nqe/network_id_unittest.cc",
+        "net/nqe/network_qualities_prefs_manager_unittest.cc",
+        "net/nqe/network_quality_estimator_params_unittest.cc",
+        "net/nqe/network_quality_estimator_unittest.cc",
+        "net/nqe/network_quality_estimator_util_unittest.cc",
+        "net/nqe/network_quality_store_unittest.cc",
+        "net/nqe/observation_buffer_unittest.cc",
+        "net/nqe/socket_watcher_unittest.cc",
+        "net/nqe/throughput_analyzer_unittest.cc",
+        "net/ntlm/ntlm_buffer_reader_unittest.cc",
+        "net/ntlm/ntlm_buffer_writer_unittest.cc",
+        "net/ntlm/ntlm_client_unittest.cc",
+        "net/ntlm/ntlm_unittest.cc",
+        "net/proxy_resolution/configured_proxy_resolution_service_unittest.cc",
+        "net/proxy_resolution/multi_threaded_proxy_resolver_unittest.cc",
+        "net/proxy_resolution/network_delegate_error_observer_unittest.cc",
+        "net/proxy_resolution/pac_file_decider_unittest.cc",
+        "net/proxy_resolution/pac_file_fetcher_impl_unittest.cc",
+        "net/proxy_resolution/proxy_bypass_rules_unittest.cc",
+        "net/proxy_resolution/proxy_config_service_android_unittest.cc",
+        "net/proxy_resolution/proxy_config_unittest.cc",
+        "net/proxy_resolution/proxy_info_unittest.cc",
+        "net/proxy_resolution/proxy_list_unittest.cc",
+        "net/quic/bidirectional_stream_quic_impl_unittest.cc",
+        "net/quic/crypto/proof_test_chromium.cc",
+        "net/quic/crypto/proof_verifier_chromium_test.cc",
+        "net/quic/dedicated_web_transport_http3_client_test.cc",
+        "net/quic/mock_quic_data.cc",
+        "net/quic/network_connection_unittest.cc",
+        "net/quic/platform/impl/quic_chromium_clock_test.cc",
+        "net/quic/properties_based_quic_server_info_test.cc",
+        "net/quic/quic_address_mismatch_test.cc",
+        "net/quic/quic_chromium_alarm_factory_test.cc",
+        "net/quic/quic_chromium_client_session_peer.cc",
+        "net/quic/quic_chromium_client_session_test.cc",
+        "net/quic/quic_chromium_client_stream_test.cc",
+        "net/quic/quic_chromium_connection_helper_test.cc",
+        "net/quic/quic_clock_skew_detector_test.cc",
+        "net/quic/quic_end_to_end_unittest.cc",
+        "net/quic/quic_http_stream_test.cc",
+        "net/quic/quic_http_utils_test.cc",
+        "net/quic/quic_network_transaction_unittest.cc",
+        "net/quic/quic_proxy_client_socket_unittest.cc",
+        "net/quic/quic_stream_factory_peer.cc",
+        "net/quic/quic_stream_factory_test.cc",
+        "net/quic/quic_test_packet_maker.cc",
+        "net/quic/set_quic_flag_test.cc",
+        "net/quic/test_quic_crypto_client_config_handle.cc",
+        "net/reporting/mock_persistent_reporting_store_unittest.cc",
+        "net/reporting/reporting_browsing_data_remover_unittest.cc",
+        "net/reporting/reporting_cache_unittest.cc",
+        "net/reporting/reporting_delivery_agent_unittest.cc",
+        "net/reporting/reporting_endpoint_manager_unittest.cc",
+        "net/reporting/reporting_garbage_collector_unittest.cc",
+        "net/reporting/reporting_header_parser_unittest.cc",
+        "net/reporting/reporting_network_change_observer_unittest.cc",
+        "net/reporting/reporting_service_unittest.cc",
+        "net/reporting/reporting_uploader_unittest.cc",
+        "net/socket/client_socket_pool_base_unittest.cc",
+        "net/socket/client_socket_pool_unittest.cc",
+        "net/socket/connect_job_factory_unittest.cc",
+        "net/socket/connect_job_test_util.cc",
+        "net/socket/connect_job_unittest.cc",
+        "net/socket/mock_client_socket_pool_manager.cc",
+        "net/socket/sequenced_socket_data_unittest.cc",
+        "net/socket/socket_bio_adapter_unittest.cc",
+        "net/socket/socket_tag_unittest.cc",
+        "net/socket/socks5_client_socket_unittest.cc",
+        "net/socket/socks_client_socket_unittest.cc",
+        "net/socket/socks_connect_job_unittest.cc",
+        "net/socket/ssl_client_socket_unittest.cc",
+        "net/socket/ssl_connect_job_unittest.cc",
+        "net/socket/ssl_server_socket_unittest.cc",
+        "net/socket/tcp_client_socket_unittest.cc",
+        "net/socket/tcp_server_socket_unittest.cc",
+        "net/socket/tcp_socket_unittest.cc",
+        "net/socket/transport_client_socket_pool_test_util.cc",
+        "net/socket/transport_client_socket_pool_unittest.cc",
+        "net/socket/transport_client_socket_unittest.cc",
+        "net/socket/transport_connect_job_unittest.cc",
+        "net/socket/udp_socket_unittest.cc",
+        "net/socket/unix_domain_client_socket_posix_unittest.cc",
+        "net/socket/unix_domain_server_socket_posix_unittest.cc",
+        "net/socket/websocket_endpoint_lock_manager_unittest.cc",
+        "net/socket/websocket_transport_client_socket_pool_unittest.cc",
+        "net/spdy/alps_decoder_test.cc",
+        "net/spdy/bidirectional_stream_spdy_impl_unittest.cc",
+        "net/spdy/buffered_spdy_framer_unittest.cc",
+        "net/spdy/fuzzing/hpack_fuzz_util_test.cc",
+        "net/spdy/header_coalescer_test.cc",
+        "net/spdy/http2_priority_dependencies_unittest.cc",
+        "net/spdy/http2_push_promise_index_test.cc",
+        "net/spdy/spdy_buffer_unittest.cc",
+        "net/spdy/spdy_http_stream_unittest.cc",
+        "net/spdy/spdy_http_utils_unittest.cc",
+        "net/spdy/spdy_log_util_unittest.cc",
+        "net/spdy/spdy_network_transaction_unittest.cc",
+        "net/spdy/spdy_proxy_client_socket_unittest.cc",
+        "net/spdy/spdy_read_queue_unittest.cc",
+        "net/spdy/spdy_session_pool_unittest.cc",
+        "net/spdy/spdy_session_test_util.cc",
+        "net/spdy/spdy_session_unittest.cc",
+        "net/spdy/spdy_stream_test_util.cc",
+        "net/spdy/spdy_stream_unittest.cc",
+        "net/spdy/spdy_write_queue_unittest.cc",
+        "net/ssl/client_cert_identity_unittest.cc",
+        "net/ssl/ssl_cipher_suite_names_unittest.cc",
+        "net/ssl/ssl_client_auth_cache_unittest.cc",
+        "net/ssl/ssl_client_session_cache_unittest.cc",
+        "net/ssl/ssl_config_service_unittest.cc",
+        "net/ssl/ssl_config_unittest.cc",
+        "net/ssl/ssl_connection_status_flags_unittest.cc",
+        "net/ssl/ssl_platform_key_android_unittest.cc",
+        "net/ssl/ssl_platform_key_util_unittest.cc",
+        "net/test/embedded_test_server/embedded_test_server_unittest.cc",
+        "net/test/embedded_test_server/http_request_unittest.cc",
+        "net/test/embedded_test_server/http_response_unittest.cc",
+        "net/test/run_all_unittests.cc",
+        "net/third_party/uri_template/uri_template_test.cc",
+        "net/tools/content_decoder_tool/content_decoder_tool.cc",
+        "net/tools/content_decoder_tool/content_decoder_tool_unittest.cc",
+        "net/tools/quic/quic_simple_client_test.cc",
+        "net/tools/tld_cleanup/tld_cleanup_util_unittest.cc",
+        "net/url_request/http_with_dns_over_https_unittest.cc",
+        "net/url_request/redirect_info_unittest.cc",
+        "net/url_request/redirect_util_unittest.cc",
+        "net/url_request/report_sender_unittest.cc",
+        "net/url_request/url_request_context_builder_unittest.cc",
+        "net/url_request/url_request_filter_unittest.cc",
+        "net/url_request/url_request_http_job_unittest.cc",
+        "net/url_request/url_request_job_factory_unittest.cc",
+        "net/url_request/url_request_quic_unittest.cc",
+        "net/url_request/url_request_throttler_simulation_unittest.cc",
+        "net/url_request/url_request_throttler_test_support.cc",
+        "net/url_request/url_request_throttler_unittest.cc",
+        "net/url_request/url_request_unittest.cc",
+        "net/url_request/view_cache_helper_unittest.cc",
+    ],
+    shared_libs: [
+        "libandroid",
+        "liblog",
+        "libz",
+    ],
+    static_libs: [
+        "cronet_aml_base_allocator_partition_allocator_partition_alloc__testing",
+        "cronet_aml_base_base__testing",
+        "cronet_aml_base_base_static__testing",
+        "cronet_aml_base_i18n__testing",
+        "cronet_aml_base_test_test_config__testing",
+        "cronet_aml_base_test_test_support__testing",
+        "cronet_aml_base_third_party_double_conversion_double_conversion__testing",
+        "cronet_aml_base_third_party_dynamic_annotations_dynamic_annotations__testing",
+        "cronet_aml_crypto_crypto__testing",
+        "cronet_aml_crypto_test_support__testing",
+        "cronet_aml_net_gtest_util__testing",
+        "cronet_aml_net_net__testing",
+        "cronet_aml_net_preload_decoder__testing",
+        "cronet_aml_net_test_support__testing",
+        "cronet_aml_net_third_party_quiche_quiche__testing",
+        "cronet_aml_net_third_party_quiche_quiche_test_support__testing",
+        "cronet_aml_net_third_party_quiche_quiche_tool_support__testing",
+        "cronet_aml_net_uri_template__testing",
+        "cronet_aml_testing_gtest_gtest__testing",
+        "cronet_aml_third_party_boringssl_boringssl__testing",
+        "cronet_aml_third_party_brotli_common__testing",
+        "cronet_aml_third_party_brotli_dec__testing",
+        "cronet_aml_third_party_ced_ced__testing",
+        "cronet_aml_third_party_icu_icui18n__testing",
+        "cronet_aml_third_party_icu_icuuc_private__testing",
+        "cronet_aml_third_party_libevent_libevent__testing",
+        "cronet_aml_third_party_libxml_libxml__testing",
+        "cronet_aml_third_party_libxml_libxml_utils__testing",
+        "cronet_aml_third_party_libxml_xml_reader__testing",
+        "cronet_aml_third_party_modp_b64_modp_b64__testing",
+        "cronet_aml_third_party_protobuf_protobuf_lite__testing",
+        "cronet_aml_url_url__testing",
+    ],
+    generated_headers: [
+        "cronet_aml_base_debugging_buildflags__testing",
+        "cronet_aml_base_logging_buildflags__testing",
+        "cronet_aml_build_chromeos_buildflags__testing",
+        "cronet_aml_net_base_registry_controlled_domains_lookup_strings_test_sets__testing",
+        "cronet_aml_net_base_registry_controlled_domains_registry_controlled_domains__testing",
+        "cronet_aml_net_http_transport_security_state_unittest_data__testing",
+        "cronet_aml_net_http_transport_security_state_unittest_data_default__testing",
+        "cronet_aml_net_net_test_jni_headers__testing",
+        "cronet_aml_testing_android_native_test_native_test_jni_headers__testing",
+        "cronet_aml_third_party_quic_trace_quic_trace_proto__testing_gen_headers",
+        "cronet_aml_url_buildflags__testing",
+    ],
+    export_generated_headers: [
+        "cronet_aml_base_debugging_buildflags__testing",
+        "cronet_aml_base_logging_buildflags__testing",
+        "cronet_aml_build_chromeos_buildflags__testing",
+        "cronet_aml_net_base_registry_controlled_domains_lookup_strings_test_sets__testing",
+        "cronet_aml_net_base_registry_controlled_domains_registry_controlled_domains__testing",
+        "cronet_aml_net_http_transport_security_state_unittest_data__testing",
+        "cronet_aml_net_http_transport_security_state_unittest_data_default__testing",
+        "cronet_aml_net_net_test_jni_headers__testing",
+        "cronet_aml_testing_android_native_test_native_test_jni_headers__testing",
+        "cronet_aml_third_party_quic_trace_quic_trace_proto__testing_gen_headers",
+        "cronet_aml_url_buildflags__testing",
+    ],
+    defaults: [
+        "cronet_aml_defaults",
+    ],
+    cflags: [
+        "-DANDROID",
+        "-DANDROID_NDK_VERSION_ROLL=r23_1",
+        "-DCR_CLANG_REVISION=\"llvmorg-16-init-6578-g0d30e92f-2\"",
+        "-DCR_LIBCXX_REVISION=64d36e572d3f9719c5d75011a718f33f11126851",
+        "-DDYNAMIC_ANNOTATIONS_ENABLED=0",
+        "-DGOOGLE_PROTOBUF_INTERNAL_DONATE_STEAL_INLINE=0",
+        "-DGOOGLE_PROTOBUF_NO_RTTI",
+        "-DGOOGLE_PROTOBUF_NO_STATIC_INITIALIZER",
+        "-DGTEST_API_=",
+        "-DGTEST_HAS_ABSL=1",
+        "-DGTEST_HAS_POSIX_RE=0",
+        "-DGTEST_HAS_TR1_TUPLE=0",
+        "-DGTEST_LANG_CXX11=1",
+        "-DHAVE_PTHREAD",
+        "-DHAVE_SYS_UIO_H",
+        "-DICU_UTIL_DATA_IMPL=ICU_UTIL_DATA_FILE",
+        "-DNDEBUG",
+        "-DNO_UNWIND_TABLES",
+        "-DNVALGRIND",
+        "-DOFFICIAL_BUILD",
+        "-DUNIT_TEST",
+        "-DUSE_CHROMIUM_ICU=1",
+        "-DUSE_REMOTE_TEST_SERVER",
+        "-DU_ENABLE_DYLOAD=0",
+        "-DU_ENABLE_RESOURCE_TRACING=0",
+        "-DU_ENABLE_TRACING=1",
+        "-DU_STATIC_IMPLEMENTATION",
+        "-DU_USING_ICU_NAMESPACE=0",
+        "-D_FORTIFY_SOURCE=2",
+        "-D_GNU_SOURCE",
+        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
+        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
+        "-D__STDC_CONSTANT_MACROS",
+        "-D__STDC_FORMAT_MACROS",
+        "-Oz",
+        "-fdata-sections",
+        "-ffunction-sections",
+        "-fno-asynchronous-unwind-tables",
+        "-fno-unwind-tables",
+        "-fvisibility-inlines-hidden",
+        "-fvisibility=hidden",
+        "-g1",
+    ],
+    local_include_dirs: [
+        "./",
+        "buildtools/third_party/libc++/",
+        "buildtools/third_party/libc++/trunk/include",
+        "buildtools/third_party/libc++abi/trunk/include",
+        "net/third_party/quiche/overrides/",
+        "net/third_party/quiche/src/",
+        "net/third_party/quiche/src/quiche/common/platform/default/",
+        "third_party/abseil-cpp/",
+        "third_party/boringssl/src/include/",
+        "third_party/ced/src/",
+        "third_party/googletest/custom/",
+        "third_party/googletest/src/googlemock/include/",
+        "third_party/googletest/src/googletest/include/",
+        "third_party/icu/source/common/",
+        "third_party/icu/source/i18n/",
+        "third_party/protobuf/src/",
+    ],
+    cpp_std: "c++17",
+    ldflags: [
+        "-Wl,--as-needed",
+        "-Wl,--gc-sections",
+        "-Wl,--icf=all",
+        "-Wl,--script,external/cronet/base/android/library_loader/anchor_functions.lds",
+        "-Wl,-wrap,asprintf",
+        "-Wl,-wrap,calloc",
+        "-Wl,-wrap,free",
+        "-Wl,-wrap,getcwd",
+        "-Wl,-wrap,malloc",
+        "-Wl,-wrap,malloc_usable_size",
+        "-Wl,-wrap,memalign",
+        "-Wl,-wrap,posix_memalign",
+        "-Wl,-wrap,pvalloc",
+        "-Wl,-wrap,realloc",
+        "-Wl,-wrap,realpath",
+        "-Wl,-wrap,strdup",
+        "-Wl,-wrap,strndup",
+        "-Wl,-wrap,valloc",
+        "-Wl,-wrap,vasprintf",
+    ],
+    stem: "libnet_unittests__library",
+    target: {
+        android_arm: {
+            cflags: [
+                "-fstack-protector",
+            ],
+        },
+        android_arm64: {
+            cflags: [
+                "-fstack-protector",
+                "-mno-outline",
+                "-mno-outline-atomics",
+            ],
+        },
+        android_x86: {
+            cflags: [
+                "-msse3",
+            ],
+        },
+        android_x86_64: {
+            cflags: [
+                "-fstack-protector",
+                "-msse3",
+            ],
+        },
+    },
+}
+
 // GN: //net:preload_decoder
 cc_library_static {
     name: "cronet_aml_net_preload_decoder",
@@ -14446,6 +15610,8 @@
         "net/extras/preload_data/decoder.cc",
     ],
     shared_libs: [
+        "//external/cronet/third_party/boringssl:libcrypto",
+        "//external/cronet/third_party/boringssl:libssl",
         "libandroid",
         "liblog",
     ],
@@ -14455,7 +15621,6 @@
         "cronet_aml_base_base_static",
         "cronet_aml_base_third_party_double_conversion_double_conversion",
         "cronet_aml_base_third_party_dynamic_annotations_dynamic_annotations",
-        "cronet_aml_third_party_boringssl_boringssl",
         "cronet_aml_third_party_icu_icui18n",
         "cronet_aml_third_party_icu_icuuc_private",
         "cronet_aml_third_party_libevent_libevent",
@@ -14760,6 +15925,156 @@
     },
 }
 
+// GN: //net:quic_test_tools__testing
+cc_object {
+    name: "cronet_aml_net_quic_test_tools__testing",
+    srcs: [
+        ":cronet_aml_third_party_quic_trace_quic_trace_proto__testing_gen",
+        "net/quic/crypto_test_utils_chromium.cc",
+        "net/quic/mock_crypto_client_stream.cc",
+        "net/quic/mock_crypto_client_stream_factory.cc",
+        "net/quic/mock_decrypter.cc",
+        "net/quic/mock_encrypter.cc",
+        "net/quic/mock_quic_context.cc",
+        "net/quic/test_task_runner.cc",
+    ],
+    shared_libs: [
+        "libandroid",
+        "liblog",
+        "libprotobuf-cpp-lite",
+        "libz",
+    ],
+    static_libs: [
+        "cronet_aml_base_allocator_partition_allocator_partition_alloc__testing",
+        "cronet_aml_base_base__testing",
+        "cronet_aml_base_base_static__testing",
+        "cronet_aml_base_i18n__testing",
+        "cronet_aml_base_test_test_config__testing",
+        "cronet_aml_base_test_test_support__testing",
+        "cronet_aml_base_third_party_double_conversion_double_conversion__testing",
+        "cronet_aml_base_third_party_dynamic_annotations_dynamic_annotations__testing",
+        "cronet_aml_crypto_crypto__testing",
+        "cronet_aml_crypto_test_support__testing",
+        "cronet_aml_net_gtest_util__testing",
+        "cronet_aml_net_net__testing",
+        "cronet_aml_net_preload_decoder__testing",
+        "cronet_aml_net_test_support__testing",
+        "cronet_aml_net_third_party_quiche_quiche__testing",
+        "cronet_aml_net_third_party_quiche_quiche_test_support__testing",
+        "cronet_aml_net_third_party_quiche_quiche_tool_support__testing",
+        "cronet_aml_net_uri_template__testing",
+        "cronet_aml_testing_gtest_gtest__testing",
+        "cronet_aml_third_party_boringssl_boringssl__testing",
+        "cronet_aml_third_party_brotli_common__testing",
+        "cronet_aml_third_party_brotli_dec__testing",
+        "cronet_aml_third_party_ced_ced__testing",
+        "cronet_aml_third_party_icu_icui18n__testing",
+        "cronet_aml_third_party_icu_icuuc_private__testing",
+        "cronet_aml_third_party_libevent_libevent__testing",
+        "cronet_aml_third_party_libxml_libxml__testing",
+        "cronet_aml_third_party_libxml_libxml_utils__testing",
+        "cronet_aml_third_party_libxml_xml_reader__testing",
+        "cronet_aml_third_party_modp_b64_modp_b64__testing",
+        "cronet_aml_third_party_protobuf_protobuf_lite__testing",
+        "cronet_aml_url_url__testing",
+    ],
+    generated_headers: [
+        "cronet_aml_build_chromeos_buildflags__testing",
+        "cronet_aml_third_party_quic_trace_quic_trace_proto__testing_gen_headers",
+    ],
+    defaults: [
+        "cronet_aml_defaults",
+    ],
+    cflags: [
+        "-DANDROID",
+        "-DANDROID_NDK_VERSION_ROLL=r23_1",
+        "-DCR_CLANG_REVISION=\"llvmorg-16-init-6578-g0d30e92f-2\"",
+        "-DCR_LIBCXX_REVISION=64d36e572d3f9719c5d75011a718f33f11126851",
+        "-DDYNAMIC_ANNOTATIONS_ENABLED=0",
+        "-DGOOGLE_PROTOBUF_INTERNAL_DONATE_STEAL_INLINE=0",
+        "-DGOOGLE_PROTOBUF_NO_RTTI",
+        "-DGOOGLE_PROTOBUF_NO_STATIC_INITIALIZER",
+        "-DGTEST_API_=",
+        "-DGTEST_HAS_ABSL=1",
+        "-DGTEST_HAS_POSIX_RE=0",
+        "-DGTEST_HAS_TR1_TUPLE=0",
+        "-DGTEST_LANG_CXX11=1",
+        "-DHAVE_PTHREAD",
+        "-DHAVE_SYS_UIO_H",
+        "-DICU_UTIL_DATA_IMPL=ICU_UTIL_DATA_FILE",
+        "-DNDEBUG",
+        "-DNO_UNWIND_TABLES",
+        "-DNVALGRIND",
+        "-DOFFICIAL_BUILD",
+        "-DUNIT_TEST",
+        "-DUSE_CHROMIUM_ICU=1",
+        "-DUSE_REMOTE_TEST_SERVER",
+        "-DU_ENABLE_DYLOAD=0",
+        "-DU_ENABLE_RESOURCE_TRACING=0",
+        "-DU_ENABLE_TRACING=1",
+        "-DU_STATIC_IMPLEMENTATION",
+        "-DU_USING_ICU_NAMESPACE=0",
+        "-D_FORTIFY_SOURCE=2",
+        "-D_GNU_SOURCE",
+        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
+        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
+        "-D__STDC_CONSTANT_MACROS",
+        "-D__STDC_FORMAT_MACROS",
+        "-Oz",
+        "-fdata-sections",
+        "-ffunction-sections",
+        "-fno-asynchronous-unwind-tables",
+        "-fno-unwind-tables",
+        "-fvisibility-inlines-hidden",
+        "-fvisibility=hidden",
+        "-g1",
+    ],
+    local_include_dirs: [
+        "./",
+        "buildtools/third_party/libc++/",
+        "buildtools/third_party/libc++/trunk/include",
+        "buildtools/third_party/libc++abi/trunk/include",
+        "net/third_party/quiche/overrides/",
+        "net/third_party/quiche/src/",
+        "net/third_party/quiche/src/quiche/common/platform/default/",
+        "third_party/abseil-cpp/",
+        "third_party/boringssl/src/include/",
+        "third_party/ced/src/",
+        "third_party/googletest/custom/",
+        "third_party/googletest/src/googlemock/include/",
+        "third_party/googletest/src/googletest/include/",
+        "third_party/icu/source/common/",
+        "third_party/icu/source/i18n/",
+        "third_party/protobuf/src/",
+    ],
+    cpp_std: "c++17",
+    target: {
+        android_arm: {
+            cflags: [
+                "-fstack-protector",
+            ],
+        },
+        android_arm64: {
+            cflags: [
+                "-fstack-protector",
+                "-mno-outline",
+                "-mno-outline-atomics",
+            ],
+        },
+        android_x86: {
+            cflags: [
+                "-msse3",
+            ],
+        },
+        android_x86_64: {
+            cflags: [
+                "-fstack-protector",
+                "-msse3",
+            ],
+        },
+    },
+}
+
 // GN: //net:simple_quic_tools__testing
 cc_object {
     name: "cronet_aml_net_simple_quic_tools__testing",
@@ -14872,6 +16187,124 @@
     },
 }
 
+// GN: //net:spdy_test_tools__testing
+cc_object {
+    name: "cronet_aml_net_spdy_test_tools__testing",
+    srcs: [
+        "net/spdy/fuzzing/hpack_fuzz_util.cc",
+    ],
+    shared_libs: [
+        "libandroid",
+        "liblog",
+        "libz",
+    ],
+    static_libs: [
+        "cronet_aml_base_allocator_partition_allocator_partition_alloc__testing",
+        "cronet_aml_base_base__testing",
+        "cronet_aml_base_base_static__testing",
+        "cronet_aml_base_third_party_double_conversion_double_conversion__testing",
+        "cronet_aml_base_third_party_dynamic_annotations_dynamic_annotations__testing",
+        "cronet_aml_crypto_crypto__testing",
+        "cronet_aml_net_net__testing",
+        "cronet_aml_net_preload_decoder__testing",
+        "cronet_aml_net_third_party_quiche_quiche__testing",
+        "cronet_aml_net_uri_template__testing",
+        "cronet_aml_testing_gtest_gtest__testing",
+        "cronet_aml_third_party_boringssl_boringssl__testing",
+        "cronet_aml_third_party_brotli_common__testing",
+        "cronet_aml_third_party_brotli_dec__testing",
+        "cronet_aml_third_party_icu_icui18n__testing",
+        "cronet_aml_third_party_icu_icuuc_private__testing",
+        "cronet_aml_third_party_libevent_libevent__testing",
+        "cronet_aml_third_party_modp_b64_modp_b64__testing",
+        "cronet_aml_third_party_protobuf_protobuf_lite__testing",
+        "cronet_aml_url_url__testing",
+    ],
+    generated_headers: [
+        "cronet_aml_build_chromeos_buildflags__testing",
+    ],
+    defaults: [
+        "cronet_aml_defaults",
+    ],
+    cflags: [
+        "-DANDROID",
+        "-DANDROID_NDK_VERSION_ROLL=r23_1",
+        "-DCR_CLANG_REVISION=\"llvmorg-16-init-6578-g0d30e92f-2\"",
+        "-DCR_LIBCXX_REVISION=64d36e572d3f9719c5d75011a718f33f11126851",
+        "-DDYNAMIC_ANNOTATIONS_ENABLED=0",
+        "-DGOOGLE_PROTOBUF_INTERNAL_DONATE_STEAL_INLINE=0",
+        "-DGOOGLE_PROTOBUF_NO_RTTI",
+        "-DGOOGLE_PROTOBUF_NO_STATIC_INITIALIZER",
+        "-DGTEST_API_=",
+        "-DGTEST_HAS_ABSL=1",
+        "-DGTEST_HAS_POSIX_RE=0",
+        "-DGTEST_HAS_TR1_TUPLE=0",
+        "-DGTEST_LANG_CXX11=1",
+        "-DHAVE_PTHREAD",
+        "-DHAVE_SYS_UIO_H",
+        "-DNDEBUG",
+        "-DNO_UNWIND_TABLES",
+        "-DNVALGRIND",
+        "-DOFFICIAL_BUILD",
+        "-DUNIT_TEST",
+        "-D_FORTIFY_SOURCE=2",
+        "-D_GNU_SOURCE",
+        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
+        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
+        "-D__STDC_CONSTANT_MACROS",
+        "-D__STDC_FORMAT_MACROS",
+        "-Oz",
+        "-fdata-sections",
+        "-ffunction-sections",
+        "-fno-asynchronous-unwind-tables",
+        "-fno-unwind-tables",
+        "-fvisibility-inlines-hidden",
+        "-fvisibility=hidden",
+        "-g1",
+    ],
+    local_include_dirs: [
+        "./",
+        "buildtools/third_party/libc++/",
+        "buildtools/third_party/libc++/trunk/include",
+        "buildtools/third_party/libc++abi/trunk/include",
+        "net/third_party/quiche/overrides/",
+        "net/third_party/quiche/src/",
+        "net/third_party/quiche/src/quiche/common/platform/default/",
+        "third_party/abseil-cpp/",
+        "third_party/boringssl/src/include/",
+        "third_party/googletest/custom/",
+        "third_party/googletest/src/googlemock/include/",
+        "third_party/googletest/src/googletest/include/",
+        "third_party/protobuf/src/",
+    ],
+    cpp_std: "c++17",
+    target: {
+        android_arm: {
+            cflags: [
+                "-fstack-protector",
+            ],
+        },
+        android_arm64: {
+            cflags: [
+                "-fstack-protector",
+                "-mno-outline",
+                "-mno-outline-atomics",
+            ],
+        },
+        android_x86: {
+            cflags: [
+                "-msse3",
+            ],
+        },
+        android_x86_64: {
+            cflags: [
+                "-fstack-protector",
+                "-msse3",
+            ],
+        },
+    },
+}
+
 // GN: //net:test_support__testing
 cc_library_static {
     name: "cronet_aml_net_test_support__testing",
@@ -15044,10 +16477,12 @@
     ],
     generated_headers: [
         "cronet_aml_build_chromeos_buildflags__testing",
+        "cronet_aml_net_cronet_buildflags__testing",
         "cronet_aml_net_http_transport_security_state_unittest_data_default__testing",
     ],
     export_generated_headers: [
         "cronet_aml_build_chromeos_buildflags__testing",
+        "cronet_aml_net_cronet_buildflags__testing",
         "cronet_aml_net_http_transport_security_state_unittest_data_default__testing",
     ],
     defaults: [
@@ -15221,6 +16656,10 @@
         "net/third_party/quiche/src/quiche/quic/core/proto/crypto_server_config.proto",
         "net/third_party/quiche/src/quiche/quic/core/proto/source_address_token.proto",
     ],
+    shared_libs: [
+        "//external/cronet/third_party/boringssl:libcrypto",
+        "//external/cronet/third_party/boringssl:libssl",
+    ],
     tools: [
         "cronet_aml_third_party_protobuf_protoc",
     ],
@@ -15295,6 +16734,7 @@
     ],
     export_include_dirs: [
         ".",
+        "net/third_party/quiche/src",
         "net/third_party/quiche/src/quiche/quic/test_tools",
         "protos",
     ],
@@ -15336,6 +16776,7 @@
     ],
     export_include_dirs: [
         ".",
+        "net/third_party/quiche/src",
         "net/third_party/quiche/src/quiche/quic/test_tools",
         "protos",
     ],
@@ -15678,6 +17119,8 @@
         "net/third_party/quiche/src/quiche/spdy/core/spdy_simple_arena.cc",
     ],
     shared_libs: [
+        "//external/cronet/third_party/boringssl:libcrypto",
+        "//external/cronet/third_party/boringssl:libssl",
         "libandroid",
         "liblog",
         "libprotobuf-cpp-lite",
@@ -15690,7 +17133,6 @@
         "cronet_aml_base_third_party_double_conversion_double_conversion",
         "cronet_aml_base_third_party_dynamic_annotations_dynamic_annotations",
         "cronet_aml_net_uri_template",
-        "cronet_aml_third_party_boringssl_boringssl",
         "cronet_aml_third_party_icu_icui18n",
         "cronet_aml_third_party_icu_icuuc_private",
         "cronet_aml_third_party_libevent_libevent",
@@ -16258,6 +17700,681 @@
     },
 }
 
+// GN: //net/third_party/quiche:quiche_test_support__testing
+cc_library_static {
+    name: "cronet_aml_net_third_party_quiche_quiche_test_support__testing",
+    srcs: [
+        ":cronet_aml_net_quic_test_flags_utils__testing",
+        ":cronet_aml_third_party_abseil_cpp_absl_base_base__testing",
+        ":cronet_aml_third_party_abseil_cpp_absl_base_log_severity__testing",
+        ":cronet_aml_third_party_abseil_cpp_absl_base_malloc_internal__testing",
+        ":cronet_aml_third_party_abseil_cpp_absl_base_raw_logging_internal__testing",
+        ":cronet_aml_third_party_abseil_cpp_absl_base_spinlock_wait__testing",
+        ":cronet_aml_third_party_abseil_cpp_absl_base_strerror__testing",
+        ":cronet_aml_third_party_abseil_cpp_absl_base_throw_delegate__testing",
+        ":cronet_aml_third_party_abseil_cpp_absl_container_hashtablez_sampler__testing",
+        ":cronet_aml_third_party_abseil_cpp_absl_container_raw_hash_set__testing",
+        ":cronet_aml_third_party_abseil_cpp_absl_debugging_debugging_internal__testing",
+        ":cronet_aml_third_party_abseil_cpp_absl_debugging_demangle_internal__testing",
+        ":cronet_aml_third_party_abseil_cpp_absl_debugging_examine_stack__testing",
+        ":cronet_aml_third_party_abseil_cpp_absl_debugging_failure_signal_handler__testing",
+        ":cronet_aml_third_party_abseil_cpp_absl_debugging_stacktrace__testing",
+        ":cronet_aml_third_party_abseil_cpp_absl_debugging_symbolize__testing",
+        ":cronet_aml_third_party_abseil_cpp_absl_hash_city__testing",
+        ":cronet_aml_third_party_abseil_cpp_absl_hash_hash__testing",
+        ":cronet_aml_third_party_abseil_cpp_absl_hash_low_level_hash__testing",
+        ":cronet_aml_third_party_abseil_cpp_absl_numeric_int128__testing",
+        ":cronet_aml_third_party_abseil_cpp_absl_profiling_exponential_biased__testing",
+        ":cronet_aml_third_party_abseil_cpp_absl_random_distributions__testing",
+        ":cronet_aml_third_party_abseil_cpp_absl_random_internal_platform__testing",
+        ":cronet_aml_third_party_abseil_cpp_absl_random_internal_pool_urbg__testing",
+        ":cronet_aml_third_party_abseil_cpp_absl_random_internal_randen__testing",
+        ":cronet_aml_third_party_abseil_cpp_absl_random_internal_randen_hwaes__testing",
+        ":cronet_aml_third_party_abseil_cpp_absl_random_internal_randen_hwaes_impl__testing",
+        ":cronet_aml_third_party_abseil_cpp_absl_random_internal_randen_slow__testing",
+        ":cronet_aml_third_party_abseil_cpp_absl_random_internal_seed_material__testing",
+        ":cronet_aml_third_party_abseil_cpp_absl_random_seed_gen_exception__testing",
+        ":cronet_aml_third_party_abseil_cpp_absl_random_seed_sequences__testing",
+        ":cronet_aml_third_party_abseil_cpp_absl_status_status__testing",
+        ":cronet_aml_third_party_abseil_cpp_absl_status_statusor__testing",
+        ":cronet_aml_third_party_abseil_cpp_absl_strings_cord__testing",
+        ":cronet_aml_third_party_abseil_cpp_absl_strings_cord_internal__testing",
+        ":cronet_aml_third_party_abseil_cpp_absl_strings_cordz_functions__testing",
+        ":cronet_aml_third_party_abseil_cpp_absl_strings_cordz_handle__testing",
+        ":cronet_aml_third_party_abseil_cpp_absl_strings_cordz_info__testing",
+        ":cronet_aml_third_party_abseil_cpp_absl_strings_internal__testing",
+        ":cronet_aml_third_party_abseil_cpp_absl_strings_str_format_internal__testing",
+        ":cronet_aml_third_party_abseil_cpp_absl_strings_strings__testing",
+        ":cronet_aml_third_party_abseil_cpp_absl_synchronization_graphcycles_internal__testing",
+        ":cronet_aml_third_party_abseil_cpp_absl_synchronization_synchronization__testing",
+        ":cronet_aml_third_party_abseil_cpp_absl_time_internal_cctz_civil_time__testing",
+        ":cronet_aml_third_party_abseil_cpp_absl_time_internal_cctz_time_zone__testing",
+        ":cronet_aml_third_party_abseil_cpp_absl_time_time__testing",
+        ":cronet_aml_third_party_abseil_cpp_absl_types_bad_optional_access__testing",
+        ":cronet_aml_third_party_abseil_cpp_absl_types_bad_variant_access__testing",
+        ":cronet_aml_third_party_googletest_gmock__testing",
+        ":cronet_aml_third_party_googletest_gtest__testing",
+        ":cronet_aml_third_party_quic_trace_quic_trace_proto__testing_gen",
+        "net/third_party/quiche/overrides/quiche_platform_impl/quiche_test_helpers_impl.cc",
+        "net/third_party/quiche/overrides/quiche_platform_impl/quiche_test_impl.cc",
+        "net/third_party/quiche/overrides/quiche_platform_impl/quiche_test_output_impl.cc",
+        "net/third_party/quiche/src/quiche/common/platform/api/quiche_test_loopback.cc",
+        "net/third_party/quiche/src/quiche/common/platform/default/quiche_platform_impl/quiche_test_loopback_impl.cc",
+        "net/third_party/quiche/src/quiche/common/test_tools/quiche_test_utils.cc",
+        "net/third_party/quiche/src/quiche/http2/adapter/test_frame_sequence.cc",
+        "net/third_party/quiche/src/quiche/http2/adapter/test_utils.cc",
+        "net/third_party/quiche/src/quiche/http2/test_tools/frame_decoder_state_test_util.cc",
+        "net/third_party/quiche/src/quiche/http2/test_tools/frame_parts.cc",
+        "net/third_party/quiche/src/quiche/http2/test_tools/frame_parts_collector.cc",
+        "net/third_party/quiche/src/quiche/http2/test_tools/frame_parts_collector_listener.cc",
+        "net/third_party/quiche/src/quiche/http2/test_tools/hpack_block_builder.cc",
+        "net/third_party/quiche/src/quiche/http2/test_tools/hpack_block_collector.cc",
+        "net/third_party/quiche/src/quiche/http2/test_tools/hpack_entry_collector.cc",
+        "net/third_party/quiche/src/quiche/http2/test_tools/hpack_example.cc",
+        "net/third_party/quiche/src/quiche/http2/test_tools/hpack_string_collector.cc",
+        "net/third_party/quiche/src/quiche/http2/test_tools/http2_constants_test_util.cc",
+        "net/third_party/quiche/src/quiche/http2/test_tools/http2_frame_builder.cc",
+        "net/third_party/quiche/src/quiche/http2/test_tools/http2_frame_decoder_listener_test_util.cc",
+        "net/third_party/quiche/src/quiche/http2/test_tools/http2_random.cc",
+        "net/third_party/quiche/src/quiche/http2/test_tools/http2_structure_decoder_test_util.cc",
+        "net/third_party/quiche/src/quiche/http2/test_tools/http2_structures_test_util.cc",
+        "net/third_party/quiche/src/quiche/http2/test_tools/payload_decoder_base_test_util.cc",
+        "net/third_party/quiche/src/quiche/http2/test_tools/random_decoder_test_base.cc",
+        "net/third_party/quiche/src/quiche/http2/test_tools/random_util.cc",
+        "net/third_party/quiche/src/quiche/quic/core/quic_trace_visitor.cc",
+        "net/third_party/quiche/src/quiche/quic/test_tools/bad_packet_writer.cc",
+        "net/third_party/quiche/src/quiche/quic/test_tools/crypto_test_utils.cc",
+        "net/third_party/quiche/src/quiche/quic/test_tools/failing_proof_source.cc",
+        "net/third_party/quiche/src/quiche/quic/test_tools/fake_proof_source.cc",
+        "net/third_party/quiche/src/quiche/quic/test_tools/first_flight.cc",
+        "net/third_party/quiche/src/quiche/quic/test_tools/limited_mtu_test_writer.cc",
+        "net/third_party/quiche/src/quiche/quic/test_tools/mock_clock.cc",
+        "net/third_party/quiche/src/quiche/quic/test_tools/mock_quic_client_promised_info.cc",
+        "net/third_party/quiche/src/quiche/quic/test_tools/mock_quic_dispatcher.cc",
+        "net/third_party/quiche/src/quiche/quic/test_tools/mock_quic_session_visitor.cc",
+        "net/third_party/quiche/src/quiche/quic/test_tools/mock_quic_spdy_client_stream.cc",
+        "net/third_party/quiche/src/quiche/quic/test_tools/mock_quic_time_wait_list_manager.cc",
+        "net/third_party/quiche/src/quiche/quic/test_tools/mock_random.cc",
+        "net/third_party/quiche/src/quiche/quic/test_tools/packet_dropping_test_writer.cc",
+        "net/third_party/quiche/src/quiche/quic/test_tools/packet_reordering_writer.cc",
+        "net/third_party/quiche/src/quiche/quic/test_tools/qpack/qpack_decoder_test_utils.cc",
+        "net/third_party/quiche/src/quiche/quic/test_tools/qpack/qpack_encoder_peer.cc",
+        "net/third_party/quiche/src/quiche/quic/test_tools/qpack/qpack_offline_decoder.cc",
+        "net/third_party/quiche/src/quiche/quic/test_tools/qpack/qpack_test_utils.cc",
+        "net/third_party/quiche/src/quiche/quic/test_tools/quic_buffered_packet_store_peer.cc",
+        "net/third_party/quiche/src/quiche/quic/test_tools/quic_client_promised_info_peer.cc",
+        "net/third_party/quiche/src/quiche/quic/test_tools/quic_coalesced_packet_peer.cc",
+        "net/third_party/quiche/src/quiche/quic/test_tools/quic_config_peer.cc",
+        "net/third_party/quiche/src/quiche/quic/test_tools/quic_connection_peer.cc",
+        "net/third_party/quiche/src/quiche/quic/test_tools/quic_crypto_server_config_peer.cc",
+        "net/third_party/quiche/src/quiche/quic/test_tools/quic_dispatcher_peer.cc",
+        "net/third_party/quiche/src/quiche/quic/test_tools/quic_flow_controller_peer.cc",
+        "net/third_party/quiche/src/quiche/quic/test_tools/quic_framer_peer.cc",
+        "net/third_party/quiche/src/quiche/quic/test_tools/quic_packet_creator_peer.cc",
+        "net/third_party/quiche/src/quiche/quic/test_tools/quic_path_validator_peer.cc",
+        "net/third_party/quiche/src/quiche/quic/test_tools/quic_sent_packet_manager_peer.cc",
+        "net/third_party/quiche/src/quiche/quic/test_tools/quic_session_peer.cc",
+        "net/third_party/quiche/src/quiche/quic/test_tools/quic_spdy_session_peer.cc",
+        "net/third_party/quiche/src/quiche/quic/test_tools/quic_spdy_stream_peer.cc",
+        "net/third_party/quiche/src/quiche/quic/test_tools/quic_stream_id_manager_peer.cc",
+        "net/third_party/quiche/src/quiche/quic/test_tools/quic_stream_peer.cc",
+        "net/third_party/quiche/src/quiche/quic/test_tools/quic_stream_send_buffer_peer.cc",
+        "net/third_party/quiche/src/quiche/quic/test_tools/quic_stream_sequencer_buffer_peer.cc",
+        "net/third_party/quiche/src/quiche/quic/test_tools/quic_stream_sequencer_peer.cc",
+        "net/third_party/quiche/src/quiche/quic/test_tools/quic_sustained_bandwidth_recorder_peer.cc",
+        "net/third_party/quiche/src/quiche/quic/test_tools/quic_test_backend.cc",
+        "net/third_party/quiche/src/quiche/quic/test_tools/quic_test_utils.cc",
+        "net/third_party/quiche/src/quiche/quic/test_tools/quic_time_wait_list_manager_peer.cc",
+        "net/third_party/quiche/src/quiche/quic/test_tools/quic_unacked_packet_map_peer.cc",
+        "net/third_party/quiche/src/quiche/quic/test_tools/rtt_stats_peer.cc",
+        "net/third_party/quiche/src/quiche/quic/test_tools/send_algorithm_test_utils.cc",
+        "net/third_party/quiche/src/quiche/quic/test_tools/simple_data_producer.cc",
+        "net/third_party/quiche/src/quiche/quic/test_tools/simple_quic_framer.cc",
+        "net/third_party/quiche/src/quiche/quic/test_tools/simple_session_cache.cc",
+        "net/third_party/quiche/src/quiche/quic/test_tools/simple_session_notifier.cc",
+        "net/third_party/quiche/src/quiche/quic/test_tools/simulator/actor.cc",
+        "net/third_party/quiche/src/quiche/quic/test_tools/simulator/alarm_factory.cc",
+        "net/third_party/quiche/src/quiche/quic/test_tools/simulator/link.cc",
+        "net/third_party/quiche/src/quiche/quic/test_tools/simulator/packet_filter.cc",
+        "net/third_party/quiche/src/quiche/quic/test_tools/simulator/port.cc",
+        "net/third_party/quiche/src/quiche/quic/test_tools/simulator/queue.cc",
+        "net/third_party/quiche/src/quiche/quic/test_tools/simulator/quic_endpoint.cc",
+        "net/third_party/quiche/src/quiche/quic/test_tools/simulator/quic_endpoint_base.cc",
+        "net/third_party/quiche/src/quiche/quic/test_tools/simulator/simulator.cc",
+        "net/third_party/quiche/src/quiche/quic/test_tools/simulator/switch.cc",
+        "net/third_party/quiche/src/quiche/quic/test_tools/simulator/traffic_policer.cc",
+        "net/third_party/quiche/src/quiche/quic/test_tools/test_certificates.cc",
+        "net/third_party/quiche/src/quiche/quic/test_tools/test_ticket_crypter.cc",
+        "net/third_party/quiche/src/quiche/quic/test_tools/web_transport_resets_backend.cc",
+        "net/third_party/quiche/src/quiche/spdy/test_tools/mock_spdy_framer_visitor.cc",
+        "net/third_party/quiche/src/quiche/spdy/test_tools/spdy_test_utils.cc",
+    ],
+    shared_libs: [
+        "libandroid",
+        "liblog",
+        "libprotobuf-cpp-lite",
+        "libz",
+    ],
+    static_libs: [
+        "cronet_aml_base_allocator_partition_allocator_partition_alloc__testing",
+        "cronet_aml_base_base__testing",
+        "cronet_aml_base_base_static__testing",
+        "cronet_aml_base_i18n__testing",
+        "cronet_aml_base_test_test_config__testing",
+        "cronet_aml_base_test_test_support__testing",
+        "cronet_aml_base_third_party_double_conversion_double_conversion__testing",
+        "cronet_aml_base_third_party_dynamic_annotations_dynamic_annotations__testing",
+        "cronet_aml_crypto_crypto__testing",
+        "cronet_aml_crypto_test_support__testing",
+        "cronet_aml_net_gtest_util__testing",
+        "cronet_aml_net_net__testing",
+        "cronet_aml_net_preload_decoder__testing",
+        "cronet_aml_net_test_support__testing",
+        "cronet_aml_net_third_party_quiche_quiche__testing",
+        "cronet_aml_net_third_party_quiche_quiche_tool_support__testing",
+        "cronet_aml_net_uri_template__testing",
+        "cronet_aml_testing_gtest_gtest__testing",
+        "cronet_aml_third_party_boringssl_boringssl__testing",
+        "cronet_aml_third_party_brotli_common__testing",
+        "cronet_aml_third_party_brotli_dec__testing",
+        "cronet_aml_third_party_ced_ced__testing",
+        "cronet_aml_third_party_icu_icui18n__testing",
+        "cronet_aml_third_party_icu_icuuc_private__testing",
+        "cronet_aml_third_party_libevent_libevent__testing",
+        "cronet_aml_third_party_libxml_libxml__testing",
+        "cronet_aml_third_party_libxml_libxml_utils__testing",
+        "cronet_aml_third_party_libxml_xml_reader__testing",
+        "cronet_aml_third_party_modp_b64_modp_b64__testing",
+        "cronet_aml_third_party_protobuf_protobuf_lite__testing",
+        "cronet_aml_url_url__testing",
+    ],
+    generated_headers: [
+        "cronet_aml_build_chromeos_buildflags__testing",
+        "cronet_aml_third_party_quic_trace_quic_trace_proto__testing_gen_headers",
+    ],
+    export_generated_headers: [
+        "cronet_aml_build_chromeos_buildflags__testing",
+        "cronet_aml_third_party_quic_trace_quic_trace_proto__testing_gen_headers",
+    ],
+    defaults: [
+        "cronet_aml_defaults",
+    ],
+    cflags: [
+        "-DANDROID",
+        "-DANDROID_NDK_VERSION_ROLL=r23_1",
+        "-DCR_CLANG_REVISION=\"llvmorg-16-init-6578-g0d30e92f-2\"",
+        "-DCR_LIBCXX_REVISION=64d36e572d3f9719c5d75011a718f33f11126851",
+        "-DDYNAMIC_ANNOTATIONS_ENABLED=0",
+        "-DGOOGLE_PROTOBUF_INTERNAL_DONATE_STEAL_INLINE=0",
+        "-DGOOGLE_PROTOBUF_NO_RTTI",
+        "-DGOOGLE_PROTOBUF_NO_STATIC_INITIALIZER",
+        "-DGTEST_API_=",
+        "-DGTEST_HAS_ABSL=1",
+        "-DGTEST_HAS_POSIX_RE=0",
+        "-DGTEST_HAS_TR1_TUPLE=0",
+        "-DGTEST_LANG_CXX11=1",
+        "-DHAVE_PTHREAD",
+        "-DHAVE_SYS_UIO_H",
+        "-DICU_UTIL_DATA_IMPL=ICU_UTIL_DATA_FILE",
+        "-DNDEBUG",
+        "-DNO_UNWIND_TABLES",
+        "-DNVALGRIND",
+        "-DOFFICIAL_BUILD",
+        "-DUNIT_TEST",
+        "-DUSE_CHROMIUM_ICU=1",
+        "-DUSE_REMOTE_TEST_SERVER",
+        "-DU_ENABLE_DYLOAD=0",
+        "-DU_ENABLE_RESOURCE_TRACING=0",
+        "-DU_ENABLE_TRACING=1",
+        "-DU_STATIC_IMPLEMENTATION",
+        "-DU_USING_ICU_NAMESPACE=0",
+        "-D_FORTIFY_SOURCE=2",
+        "-D_GNU_SOURCE",
+        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
+        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
+        "-D__STDC_CONSTANT_MACROS",
+        "-D__STDC_FORMAT_MACROS",
+        "-Oz",
+        "-fdata-sections",
+        "-ffunction-sections",
+        "-fno-asynchronous-unwind-tables",
+        "-fno-unwind-tables",
+        "-fvisibility-inlines-hidden",
+        "-fvisibility=hidden",
+        "-g1",
+    ],
+    local_include_dirs: [
+        "./",
+        "buildtools/third_party/libc++/",
+        "buildtools/third_party/libc++/trunk/include",
+        "buildtools/third_party/libc++abi/trunk/include",
+        "net/third_party/quiche/overrides/",
+        "net/third_party/quiche/src/",
+        "net/third_party/quiche/src/quiche/common/platform/default/",
+        "third_party/abseil-cpp/",
+        "third_party/boringssl/src/include/",
+        "third_party/ced/src/",
+        "third_party/googletest/custom/",
+        "third_party/googletest/src/googlemock/include/",
+        "third_party/googletest/src/googletest/include/",
+        "third_party/icu/source/common/",
+        "third_party/icu/source/i18n/",
+        "third_party/protobuf/src/",
+    ],
+    cpp_std: "c++17",
+    ldflags: [
+        "-Wl,--as-needed",
+        "-Wl,--gc-sections",
+        "-Wl,--icf=all",
+        "-Wl,--script,external/cronet/base/android/library_loader/anchor_functions.lds",
+        "-Wl,-wrap,asprintf",
+        "-Wl,-wrap,calloc",
+        "-Wl,-wrap,free",
+        "-Wl,-wrap,getcwd",
+        "-Wl,-wrap,malloc",
+        "-Wl,-wrap,malloc_usable_size",
+        "-Wl,-wrap,memalign",
+        "-Wl,-wrap,posix_memalign",
+        "-Wl,-wrap,pvalloc",
+        "-Wl,-wrap,realloc",
+        "-Wl,-wrap,realpath",
+        "-Wl,-wrap,strdup",
+        "-Wl,-wrap,strndup",
+        "-Wl,-wrap,valloc",
+        "-Wl,-wrap,vasprintf",
+    ],
+    target: {
+        android_arm: {
+            cflags: [
+                "-fstack-protector",
+            ],
+        },
+        android_arm64: {
+            cflags: [
+                "-fstack-protector",
+                "-mno-outline",
+                "-mno-outline-atomics",
+            ],
+        },
+        android_x86: {
+            cflags: [
+                "-msse3",
+            ],
+        },
+        android_x86_64: {
+            cflags: [
+                "-fstack-protector",
+                "-msse3",
+            ],
+        },
+    },
+}
+
+// GN: //net/third_party/quiche:quiche_tests__testing
+cc_object {
+    name: "cronet_aml_net_third_party_quiche_quiche_tests__testing",
+    srcs: [
+        "net/third_party/quiche/src/quiche/common/platform/api/quiche_file_utils_test.cc",
+        "net/third_party/quiche/src/quiche/common/platform/api/quiche_hostname_utils_test.cc",
+        "net/third_party/quiche/src/quiche/common/platform/api/quiche_mem_slice_test.cc",
+        "net/third_party/quiche/src/quiche/common/platform/api/quiche_reference_counted_test.cc",
+        "net/third_party/quiche/src/quiche/common/platform/api/quiche_stack_trace_test.cc",
+        "net/third_party/quiche/src/quiche/common/platform/api/quiche_time_utils_test.cc",
+        "net/third_party/quiche/src/quiche/common/platform/api/quiche_url_utils_test.cc",
+        "net/third_party/quiche/src/quiche/common/quiche_buffer_allocator_test.cc",
+        "net/third_party/quiche/src/quiche/common/quiche_circular_deque_test.cc",
+        "net/third_party/quiche/src/quiche/common/quiche_data_reader_test.cc",
+        "net/third_party/quiche/src/quiche/common/quiche_data_writer_test.cc",
+        "net/third_party/quiche/src/quiche/common/quiche_endian_test.cc",
+        "net/third_party/quiche/src/quiche/common/quiche_ip_address_test.cc",
+        "net/third_party/quiche/src/quiche/common/quiche_linked_hash_map_test.cc",
+        "net/third_party/quiche/src/quiche/common/quiche_mem_slice_storage_test.cc",
+        "net/third_party/quiche/src/quiche/common/quiche_random_test.cc",
+        "net/third_party/quiche/src/quiche/common/quiche_text_utils_test.cc",
+        "net/third_party/quiche/src/quiche/common/simple_buffer_allocator_test.cc",
+        "net/third_party/quiche/src/quiche/common/structured_headers_generated_test.cc",
+        "net/third_party/quiche/src/quiche/common/structured_headers_test.cc",
+        "net/third_party/quiche/src/quiche/http2/adapter/event_forwarder_test.cc",
+        "net/third_party/quiche/src/quiche/http2/adapter/header_validator_test.cc",
+        "net/third_party/quiche/src/quiche/http2/adapter/noop_header_validator_test.cc",
+        "net/third_party/quiche/src/quiche/http2/adapter/oghttp2_adapter_test.cc",
+        "net/third_party/quiche/src/quiche/http2/adapter/oghttp2_session_test.cc",
+        "net/third_party/quiche/src/quiche/http2/adapter/window_manager_test.cc",
+        "net/third_party/quiche/src/quiche/http2/core/priority_write_scheduler_test.cc",
+        "net/third_party/quiche/src/quiche/http2/decoder/decode_buffer_test.cc",
+        "net/third_party/quiche/src/quiche/http2/decoder/decode_http2_structures_test.cc",
+        "net/third_party/quiche/src/quiche/http2/decoder/http2_frame_decoder_test.cc",
+        "net/third_party/quiche/src/quiche/http2/decoder/http2_structure_decoder_test.cc",
+        "net/third_party/quiche/src/quiche/http2/decoder/payload_decoders/altsvc_payload_decoder_test.cc",
+        "net/third_party/quiche/src/quiche/http2/decoder/payload_decoders/continuation_payload_decoder_test.cc",
+        "net/third_party/quiche/src/quiche/http2/decoder/payload_decoders/data_payload_decoder_test.cc",
+        "net/third_party/quiche/src/quiche/http2/decoder/payload_decoders/goaway_payload_decoder_test.cc",
+        "net/third_party/quiche/src/quiche/http2/decoder/payload_decoders/headers_payload_decoder_test.cc",
+        "net/third_party/quiche/src/quiche/http2/decoder/payload_decoders/ping_payload_decoder_test.cc",
+        "net/third_party/quiche/src/quiche/http2/decoder/payload_decoders/priority_payload_decoder_test.cc",
+        "net/third_party/quiche/src/quiche/http2/decoder/payload_decoders/priority_update_payload_decoder_test.cc",
+        "net/third_party/quiche/src/quiche/http2/decoder/payload_decoders/push_promise_payload_decoder_test.cc",
+        "net/third_party/quiche/src/quiche/http2/decoder/payload_decoders/rst_stream_payload_decoder_test.cc",
+        "net/third_party/quiche/src/quiche/http2/decoder/payload_decoders/settings_payload_decoder_test.cc",
+        "net/third_party/quiche/src/quiche/http2/decoder/payload_decoders/unknown_payload_decoder_test.cc",
+        "net/third_party/quiche/src/quiche/http2/decoder/payload_decoders/window_update_payload_decoder_test.cc",
+        "net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_block_collector_test.cc",
+        "net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_block_decoder_test.cc",
+        "net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_decoder_state_test.cc",
+        "net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_decoder_string_buffer_test.cc",
+        "net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_decoder_tables_test.cc",
+        "net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_decoder_test.cc",
+        "net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_entry_collector_test.cc",
+        "net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_entry_decoder_test.cc",
+        "net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_entry_type_decoder_test.cc",
+        "net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_string_decoder_test.cc",
+        "net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_whole_entry_buffer_test.cc",
+        "net/third_party/quiche/src/quiche/http2/hpack/http2_hpack_constants_test.cc",
+        "net/third_party/quiche/src/quiche/http2/hpack/huffman/hpack_huffman_decoder_test.cc",
+        "net/third_party/quiche/src/quiche/http2/hpack/huffman/hpack_huffman_encoder_test.cc",
+        "net/third_party/quiche/src/quiche/http2/hpack/huffman/hpack_huffman_transcoder_test.cc",
+        "net/third_party/quiche/src/quiche/http2/hpack/varint/hpack_varint_decoder_test.cc",
+        "net/third_party/quiche/src/quiche/http2/hpack/varint/hpack_varint_encoder_test.cc",
+        "net/third_party/quiche/src/quiche/http2/hpack/varint/hpack_varint_round_trip_test.cc",
+        "net/third_party/quiche/src/quiche/http2/http2_constants_test.cc",
+        "net/third_party/quiche/src/quiche/http2/http2_structures_test.cc",
+        "net/third_party/quiche/src/quiche/http2/test_tools/hpack_block_builder_test.cc",
+        "net/third_party/quiche/src/quiche/http2/test_tools/http2_random_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/congestion_control/bandwidth_sampler_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/congestion_control/bbr2_simulator_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/congestion_control/bbr_sender_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/congestion_control/cubic_bytes_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/congestion_control/general_loss_algorithm_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/congestion_control/hybrid_slow_start_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/congestion_control/pacing_sender_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/congestion_control/prr_sender_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/congestion_control/rtt_stats_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/congestion_control/send_algorithm_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/congestion_control/tcp_cubic_sender_bytes_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/congestion_control/uber_loss_algorithm_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/congestion_control/windowed_filter_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/crypto/aes_128_gcm_12_decrypter_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/crypto/aes_128_gcm_12_encrypter_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/crypto/aes_128_gcm_decrypter_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/crypto/aes_128_gcm_encrypter_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/crypto/aes_256_gcm_decrypter_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/crypto/aes_256_gcm_encrypter_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/crypto/cert_compressor_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/crypto/certificate_util_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/crypto/certificate_view_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/crypto/chacha20_poly1305_decrypter_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/crypto/chacha20_poly1305_encrypter_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/crypto/chacha20_poly1305_tls_decrypter_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/crypto/chacha20_poly1305_tls_encrypter_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/crypto/channel_id_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/crypto/client_proof_source_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/crypto/crypto_framer_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/crypto/crypto_handshake_message_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/crypto/crypto_secret_boxer_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/crypto/crypto_server_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/crypto/crypto_utils_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/crypto/curve25519_key_exchange_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/crypto/null_decrypter_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/crypto/null_encrypter_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/crypto/p256_key_exchange_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/crypto/proof_source_x509_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/crypto/quic_client_session_cache_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/crypto/quic_compressed_certs_cache_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/crypto/quic_crypto_client_config_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/crypto/quic_crypto_server_config_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/crypto/quic_hkdf_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/crypto/transport_parameters_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/crypto/web_transport_fingerprint_proof_verifier_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/frames/quic_frames_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/http/capsule_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/http/http_decoder_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/http/http_encoder_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/http/http_frames_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/http/quic_client_promised_info_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/http/quic_client_push_promise_index_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/http/quic_header_list_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/http/quic_headers_stream_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/http/quic_receive_control_stream_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/http/quic_send_control_stream_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/http/quic_server_session_base_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/http/quic_spdy_session_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/http/quic_spdy_stream_body_manager_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/http/quic_spdy_stream_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/http/spdy_server_push_utils_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/http/spdy_utils_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/http/web_transport_http3_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/legacy_quic_stream_id_manager_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/packet_number_indexed_queue_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/qpack/qpack_blocking_manager_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/qpack/qpack_decoded_headers_accumulator_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/qpack/qpack_decoder_stream_receiver_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/qpack/qpack_decoder_stream_sender_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/qpack/qpack_decoder_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/qpack/qpack_encoder_stream_receiver_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/qpack/qpack_encoder_stream_sender_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/qpack/qpack_encoder_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/qpack/qpack_header_table_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/qpack/qpack_index_conversions_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/qpack/qpack_instruction_decoder_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/qpack/qpack_instruction_encoder_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/qpack/qpack_receive_stream_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/qpack/qpack_required_insert_count_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/qpack/qpack_round_trip_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/qpack/qpack_send_stream_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/qpack/qpack_static_table_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/qpack/value_splitting_header_list_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/quic_alarm_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/quic_arena_scoped_ptr_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/quic_bandwidth_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/quic_buffered_packet_store_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/quic_chaos_protector_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/quic_coalesced_packet_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/quic_config_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/quic_connection_context_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/quic_connection_id_manager_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/quic_connection_id_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/quic_connection_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/quic_control_frame_manager_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/quic_crypto_client_handshaker_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/quic_crypto_client_stream_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/quic_crypto_server_stream_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/quic_crypto_stream_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/quic_data_writer_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/quic_datagram_queue_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/quic_dispatcher_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/quic_error_codes_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/quic_flow_controller_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/quic_framer_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/quic_idle_network_detector_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/quic_interval_deque_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/quic_interval_set_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/quic_interval_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/quic_legacy_version_encapsulator_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/quic_lru_cache_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/quic_network_blackhole_detector_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/quic_one_block_arena_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/quic_packet_creator_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/quic_packet_number_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/quic_packets_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/quic_path_validator_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/quic_received_packet_manager_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/quic_sent_packet_manager_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/quic_server_id_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/quic_session_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/quic_socket_address_coder_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/quic_stream_id_manager_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/quic_stream_send_buffer_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/quic_stream_sequencer_buffer_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/quic_stream_sequencer_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/quic_stream_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/quic_sustained_bandwidth_recorder_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/quic_tag_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/quic_time_accumulator_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/quic_time_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/quic_time_wait_list_manager_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/quic_trace_visitor_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/quic_unacked_packet_map_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/quic_utils_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/quic_version_manager_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/quic_versions_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/quic_write_blocked_list_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/tls_chlo_extractor_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/tls_client_handshaker_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/uber_quic_stream_id_manager_test.cc",
+        "net/third_party/quiche/src/quiche/quic/core/uber_received_packet_manager_test.cc",
+        "net/third_party/quiche/src/quiche/quic/platform/api/quic_socket_address_test.cc",
+        "net/third_party/quiche/src/quiche/quic/test_tools/crypto_test_utils_test.cc",
+        "net/third_party/quiche/src/quiche/quic/test_tools/quic_test_utils_test.cc",
+        "net/third_party/quiche/src/quiche/quic/test_tools/simple_session_notifier_test.cc",
+        "net/third_party/quiche/src/quiche/quic/test_tools/simulator/quic_endpoint_test.cc",
+        "net/third_party/quiche/src/quiche/quic/test_tools/simulator/simulator_test.cc",
+        "net/third_party/quiche/src/quiche/quic/tools/quic_memory_cache_backend_test.cc",
+        "net/third_party/quiche/src/quiche/quic/tools/quic_tcp_like_trace_converter_test.cc",
+        "net/third_party/quiche/src/quiche/quic/tools/simple_ticket_crypter_test.cc",
+        "net/third_party/quiche/src/quiche/spdy/core/array_output_buffer_test.cc",
+        "net/third_party/quiche/src/quiche/spdy/core/hpack/hpack_decoder_adapter_test.cc",
+        "net/third_party/quiche/src/quiche/spdy/core/hpack/hpack_encoder_test.cc",
+        "net/third_party/quiche/src/quiche/spdy/core/hpack/hpack_entry_test.cc",
+        "net/third_party/quiche/src/quiche/spdy/core/hpack/hpack_header_table_test.cc",
+        "net/third_party/quiche/src/quiche/spdy/core/hpack/hpack_output_stream_test.cc",
+        "net/third_party/quiche/src/quiche/spdy/core/hpack/hpack_round_trip_test.cc",
+        "net/third_party/quiche/src/quiche/spdy/core/hpack/hpack_static_table_test.cc",
+        "net/third_party/quiche/src/quiche/spdy/core/http2_header_block_test.cc",
+        "net/third_party/quiche/src/quiche/spdy/core/http2_header_storage_test.cc",
+        "net/third_party/quiche/src/quiche/spdy/core/spdy_alt_svc_wire_format_test.cc",
+        "net/third_party/quiche/src/quiche/spdy/core/spdy_frame_builder_test.cc",
+        "net/third_party/quiche/src/quiche/spdy/core/spdy_framer_test.cc",
+        "net/third_party/quiche/src/quiche/spdy/core/spdy_intrusive_list_test.cc",
+        "net/third_party/quiche/src/quiche/spdy/core/spdy_pinnable_buffer_piece_test.cc",
+        "net/third_party/quiche/src/quiche/spdy/core/spdy_prefixed_buffer_reader_test.cc",
+        "net/third_party/quiche/src/quiche/spdy/core/spdy_protocol_test.cc",
+        "net/third_party/quiche/src/quiche/spdy/core/spdy_simple_arena_test.cc",
+    ],
+    shared_libs: [
+        "libandroid",
+        "liblog",
+        "libz",
+    ],
+    static_libs: [
+        "cronet_aml_base_allocator_partition_allocator_partition_alloc__testing",
+        "cronet_aml_base_base__testing",
+        "cronet_aml_base_base_static__testing",
+        "cronet_aml_base_i18n__testing",
+        "cronet_aml_base_test_test_config__testing",
+        "cronet_aml_base_test_test_support__testing",
+        "cronet_aml_base_third_party_double_conversion_double_conversion__testing",
+        "cronet_aml_base_third_party_dynamic_annotations_dynamic_annotations__testing",
+        "cronet_aml_crypto_crypto__testing",
+        "cronet_aml_crypto_test_support__testing",
+        "cronet_aml_net_gtest_util__testing",
+        "cronet_aml_net_net__testing",
+        "cronet_aml_net_preload_decoder__testing",
+        "cronet_aml_net_test_support__testing",
+        "cronet_aml_net_third_party_quiche_quiche__testing",
+        "cronet_aml_net_third_party_quiche_quiche_test_support__testing",
+        "cronet_aml_net_third_party_quiche_quiche_tool_support__testing",
+        "cronet_aml_net_uri_template__testing",
+        "cronet_aml_testing_gtest_gtest__testing",
+        "cronet_aml_third_party_boringssl_boringssl__testing",
+        "cronet_aml_third_party_brotli_common__testing",
+        "cronet_aml_third_party_brotli_dec__testing",
+        "cronet_aml_third_party_ced_ced__testing",
+        "cronet_aml_third_party_icu_icui18n__testing",
+        "cronet_aml_third_party_icu_icuuc_private__testing",
+        "cronet_aml_third_party_libevent_libevent__testing",
+        "cronet_aml_third_party_libxml_libxml__testing",
+        "cronet_aml_third_party_libxml_libxml_utils__testing",
+        "cronet_aml_third_party_libxml_xml_reader__testing",
+        "cronet_aml_third_party_modp_b64_modp_b64__testing",
+        "cronet_aml_third_party_protobuf_protobuf_lite__testing",
+        "cronet_aml_url_url__testing",
+    ],
+    generated_headers: [
+        "cronet_aml_build_chromeos_buildflags__testing",
+        "cronet_aml_third_party_quic_trace_quic_trace_proto__testing_gen_headers",
+    ],
+    defaults: [
+        "cronet_aml_defaults",
+    ],
+    cflags: [
+        "-DANDROID",
+        "-DANDROID_NDK_VERSION_ROLL=r23_1",
+        "-DCR_CLANG_REVISION=\"llvmorg-16-init-6578-g0d30e92f-2\"",
+        "-DCR_LIBCXX_REVISION=64d36e572d3f9719c5d75011a718f33f11126851",
+        "-DDYNAMIC_ANNOTATIONS_ENABLED=0",
+        "-DGOOGLE_PROTOBUF_INTERNAL_DONATE_STEAL_INLINE=0",
+        "-DGOOGLE_PROTOBUF_NO_RTTI",
+        "-DGOOGLE_PROTOBUF_NO_STATIC_INITIALIZER",
+        "-DGTEST_API_=",
+        "-DGTEST_HAS_ABSL=1",
+        "-DGTEST_HAS_POSIX_RE=0",
+        "-DGTEST_HAS_TR1_TUPLE=0",
+        "-DGTEST_LANG_CXX11=1",
+        "-DHAVE_PTHREAD",
+        "-DHAVE_SYS_UIO_H",
+        "-DNDEBUG",
+        "-DNO_UNWIND_TABLES",
+        "-DNVALGRIND",
+        "-DOFFICIAL_BUILD",
+        "-DUNIT_TEST",
+        "-D_FORTIFY_SOURCE=2",
+        "-D_GNU_SOURCE",
+        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
+        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
+        "-D__STDC_CONSTANT_MACROS",
+        "-D__STDC_FORMAT_MACROS",
+        "-Oz",
+        "-fdata-sections",
+        "-ffunction-sections",
+        "-fno-asynchronous-unwind-tables",
+        "-fno-unwind-tables",
+        "-fvisibility-inlines-hidden",
+        "-fvisibility=hidden",
+        "-g1",
+    ],
+    local_include_dirs: [
+        "./",
+        "buildtools/third_party/libc++/",
+        "buildtools/third_party/libc++/trunk/include",
+        "buildtools/third_party/libc++abi/trunk/include",
+        "net/third_party/quiche/overrides/",
+        "net/third_party/quiche/src/",
+        "net/third_party/quiche/src/quiche/common/platform/default/",
+        "third_party/abseil-cpp/",
+        "third_party/boringssl/src/include/",
+        "third_party/googletest/custom/",
+        "third_party/googletest/src/googlemock/include/",
+        "third_party/googletest/src/googletest/include/",
+        "third_party/protobuf/src/",
+    ],
+    cpp_std: "c++17",
+    target: {
+        android_arm: {
+            cflags: [
+                "-fstack-protector",
+            ],
+        },
+        android_arm64: {
+            cflags: [
+                "-fstack-protector",
+                "-mno-outline",
+                "-mno-outline-atomics",
+            ],
+        },
+        android_x86: {
+            cflags: [
+                "-msse3",
+            ],
+        },
+        android_x86_64: {
+            cflags: [
+                "-fstack-protector",
+                "-msse3",
+            ],
+        },
+    },
+}
+
 // GN: //net/third_party/quiche:quiche_tool_support__testing
 cc_library_static {
     name: "cronet_aml_net_third_party_quiche_quiche_tool_support__testing",
@@ -16430,41 +18547,30 @@
         "cronet_aml_third_party_modp_b64_modp_b64__testing",
     ],
     host_supported: true,
-    device_supported: false,
     defaults: [
         "cronet_aml_defaults",
     ],
     cflags: [
         "-DCR_CLANG_REVISION=\"llvmorg-16-init-6578-g0d30e92f-2\"",
         "-DCR_LIBCXX_REVISION=64d36e572d3f9719c5d75011a718f33f11126851",
-        "-DCR_SYSROOT_KEY=20220331T153654Z-0",
         "-DDYNAMIC_ANNOTATIONS_ENABLED=0",
         "-DNDEBUG",
         "-DNO_UNWIND_TABLES",
         "-DNVALGRIND",
         "-DOFFICIAL_BUILD",
-        "-DUSE_AURA=1",
-        "-DUSE_OZONE=1",
-        "-DUSE_UDEV",
-        "-D_FILE_OFFSET_BITS=64",
         "-D_FORTIFY_SOURCE=2",
         "-D_GNU_SOURCE",
-        "-D_LARGEFILE64_SOURCE",
-        "-D_LARGEFILE_SOURCE",
         "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
         "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
         "-D__STDC_CONSTANT_MACROS",
         "-D__STDC_FORMAT_MACROS",
-        "-O2",
         "-fdata-sections",
         "-ffunction-sections",
         "-fno-asynchronous-unwind-tables",
         "-fno-unwind-tables",
-        "-fstack-protector",
         "-fvisibility-inlines-hidden",
         "-fvisibility=hidden",
         "-g1",
-        "-msse3",
     ],
     local_include_dirs: [
         "./",
@@ -16474,7 +18580,68 @@
         "third_party/abseil-cpp/",
         "third_party/boringssl/src/include/",
     ],
-    cpp_std: "c++20",
+    target: {
+        android: {
+            shared_libs: [
+                "libandroid",
+                "liblog",
+            ],
+        },
+        android_arm: {
+            cflags: [
+                "-DANDROID",
+                "-DANDROID_NDK_VERSION_ROLL=r23_1",
+                "-DHAVE_SYS_UIO_H",
+                "-Oz",
+                "-fstack-protector",
+            ],
+        },
+        android_arm64: {
+            cflags: [
+                "-DANDROID",
+                "-DANDROID_NDK_VERSION_ROLL=r23_1",
+                "-DHAVE_SYS_UIO_H",
+                "-Oz",
+                "-fstack-protector",
+                "-mno-outline",
+                "-mno-outline-atomics",
+            ],
+        },
+        android_x86: {
+            cflags: [
+                "-DANDROID",
+                "-DANDROID_NDK_VERSION_ROLL=r23_1",
+                "-DHAVE_SYS_UIO_H",
+                "-Oz",
+                "-msse3",
+            ],
+        },
+        android_x86_64: {
+            cflags: [
+                "-DANDROID",
+                "-DANDROID_NDK_VERSION_ROLL=r23_1",
+                "-DHAVE_SYS_UIO_H",
+                "-Oz",
+                "-fstack-protector",
+                "-msse3",
+            ],
+        },
+        host: {
+            cflags: [
+                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
+                "-DUSE_AURA=1",
+                "-DUSE_OZONE=1",
+                "-DUSE_UDEV",
+                "-D_FILE_OFFSET_BITS=64",
+                "-D_LARGEFILE64_SOURCE",
+                "-D_LARGEFILE_SOURCE",
+                "-O2",
+                "-fstack-protector",
+                "-msse3",
+            ],
+            compile_multilib: "64",
+        },
+    },
 }
 
 // GN: //net/tools/tld_cleanup:tld_cleanup__testing
@@ -16720,6 +18887,8 @@
         "net/traffic_annotation/network_traffic_annotation_android.cc",
     ],
     shared_libs: [
+        "//external/cronet/third_party/boringssl:libcrypto",
+        "//external/cronet/third_party/boringssl:libssl",
         "libandroid",
         "liblog",
     ],
@@ -16729,7 +18898,6 @@
         "cronet_aml_base_base_static",
         "cronet_aml_base_third_party_double_conversion_double_conversion",
         "cronet_aml_base_third_party_dynamic_annotations_dynamic_annotations",
-        "cronet_aml_third_party_boringssl_boringssl",
         "cronet_aml_third_party_icu_icui18n",
         "cronet_aml_third_party_icu_icuuc_private",
         "cronet_aml_third_party_libevent_libevent",
@@ -16900,6 +19068,8 @@
         "net/third_party/uri_template/uri_template.cc",
     ],
     shared_libs: [
+        "//external/cronet/third_party/boringssl:libcrypto",
+        "//external/cronet/third_party/boringssl:libssl",
         "libandroid",
         "liblog",
     ],
@@ -16909,7 +19079,6 @@
         "cronet_aml_base_base_static",
         "cronet_aml_base_third_party_double_conversion_double_conversion",
         "cronet_aml_base_third_party_dynamic_annotations_dynamic_annotations",
-        "cronet_aml_third_party_boringssl_boringssl",
         "cronet_aml_third_party_icu_icui18n",
         "cronet_aml_third_party_icu_icuuc_private",
         "cronet_aml_third_party_libevent_libevent",
@@ -25627,348 +27796,6 @@
     },
 }
 
-// GN: //third_party/boringssl:boringssl
-cc_library_static {
-    name: "cronet_aml_third_party_boringssl_boringssl",
-    srcs: [
-        ":cronet_aml_third_party_boringssl_boringssl_asm",
-        "third_party/boringssl/err_data.c",
-        "third_party/boringssl/src/crypto/asn1/a_bitstr.c",
-        "third_party/boringssl/src/crypto/asn1/a_bool.c",
-        "third_party/boringssl/src/crypto/asn1/a_d2i_fp.c",
-        "third_party/boringssl/src/crypto/asn1/a_dup.c",
-        "third_party/boringssl/src/crypto/asn1/a_gentm.c",
-        "third_party/boringssl/src/crypto/asn1/a_i2d_fp.c",
-        "third_party/boringssl/src/crypto/asn1/a_int.c",
-        "third_party/boringssl/src/crypto/asn1/a_mbstr.c",
-        "third_party/boringssl/src/crypto/asn1/a_object.c",
-        "third_party/boringssl/src/crypto/asn1/a_octet.c",
-        "third_party/boringssl/src/crypto/asn1/a_print.c",
-        "third_party/boringssl/src/crypto/asn1/a_strex.c",
-        "third_party/boringssl/src/crypto/asn1/a_strnid.c",
-        "third_party/boringssl/src/crypto/asn1/a_time.c",
-        "third_party/boringssl/src/crypto/asn1/a_type.c",
-        "third_party/boringssl/src/crypto/asn1/a_utctm.c",
-        "third_party/boringssl/src/crypto/asn1/a_utf8.c",
-        "third_party/boringssl/src/crypto/asn1/asn1_lib.c",
-        "third_party/boringssl/src/crypto/asn1/asn1_par.c",
-        "third_party/boringssl/src/crypto/asn1/asn_pack.c",
-        "third_party/boringssl/src/crypto/asn1/f_int.c",
-        "third_party/boringssl/src/crypto/asn1/f_string.c",
-        "third_party/boringssl/src/crypto/asn1/posix_time.c",
-        "third_party/boringssl/src/crypto/asn1/tasn_dec.c",
-        "third_party/boringssl/src/crypto/asn1/tasn_enc.c",
-        "third_party/boringssl/src/crypto/asn1/tasn_fre.c",
-        "third_party/boringssl/src/crypto/asn1/tasn_new.c",
-        "third_party/boringssl/src/crypto/asn1/tasn_typ.c",
-        "third_party/boringssl/src/crypto/asn1/tasn_utl.c",
-        "third_party/boringssl/src/crypto/base64/base64.c",
-        "third_party/boringssl/src/crypto/bio/bio.c",
-        "third_party/boringssl/src/crypto/bio/bio_mem.c",
-        "third_party/boringssl/src/crypto/bio/connect.c",
-        "third_party/boringssl/src/crypto/bio/fd.c",
-        "third_party/boringssl/src/crypto/bio/file.c",
-        "third_party/boringssl/src/crypto/bio/hexdump.c",
-        "third_party/boringssl/src/crypto/bio/pair.c",
-        "third_party/boringssl/src/crypto/bio/printf.c",
-        "third_party/boringssl/src/crypto/bio/socket.c",
-        "third_party/boringssl/src/crypto/bio/socket_helper.c",
-        "third_party/boringssl/src/crypto/blake2/blake2.c",
-        "third_party/boringssl/src/crypto/bn_extra/bn_asn1.c",
-        "third_party/boringssl/src/crypto/bn_extra/convert.c",
-        "third_party/boringssl/src/crypto/buf/buf.c",
-        "third_party/boringssl/src/crypto/bytestring/asn1_compat.c",
-        "third_party/boringssl/src/crypto/bytestring/ber.c",
-        "third_party/boringssl/src/crypto/bytestring/cbb.c",
-        "third_party/boringssl/src/crypto/bytestring/cbs.c",
-        "third_party/boringssl/src/crypto/bytestring/unicode.c",
-        "third_party/boringssl/src/crypto/chacha/chacha.c",
-        "third_party/boringssl/src/crypto/cipher_extra/cipher_extra.c",
-        "third_party/boringssl/src/crypto/cipher_extra/derive_key.c",
-        "third_party/boringssl/src/crypto/cipher_extra/e_aesctrhmac.c",
-        "third_party/boringssl/src/crypto/cipher_extra/e_aesgcmsiv.c",
-        "third_party/boringssl/src/crypto/cipher_extra/e_chacha20poly1305.c",
-        "third_party/boringssl/src/crypto/cipher_extra/e_des.c",
-        "third_party/boringssl/src/crypto/cipher_extra/e_null.c",
-        "third_party/boringssl/src/crypto/cipher_extra/e_rc2.c",
-        "third_party/boringssl/src/crypto/cipher_extra/e_rc4.c",
-        "third_party/boringssl/src/crypto/cipher_extra/e_tls.c",
-        "third_party/boringssl/src/crypto/cipher_extra/tls_cbc.c",
-        "third_party/boringssl/src/crypto/conf/conf.c",
-        "third_party/boringssl/src/crypto/cpu_aarch64_apple.c",
-        "third_party/boringssl/src/crypto/cpu_aarch64_fuchsia.c",
-        "third_party/boringssl/src/crypto/cpu_aarch64_linux.c",
-        "third_party/boringssl/src/crypto/cpu_aarch64_win.c",
-        "third_party/boringssl/src/crypto/cpu_arm.c",
-        "third_party/boringssl/src/crypto/cpu_arm_linux.c",
-        "third_party/boringssl/src/crypto/cpu_intel.c",
-        "third_party/boringssl/src/crypto/cpu_ppc64le.c",
-        "third_party/boringssl/src/crypto/crypto.c",
-        "third_party/boringssl/src/crypto/curve25519/curve25519.c",
-        "third_party/boringssl/src/crypto/curve25519/spake25519.c",
-        "third_party/boringssl/src/crypto/des/des.c",
-        "third_party/boringssl/src/crypto/dh_extra/dh_asn1.c",
-        "third_party/boringssl/src/crypto/dh_extra/params.c",
-        "third_party/boringssl/src/crypto/digest_extra/digest_extra.c",
-        "third_party/boringssl/src/crypto/dsa/dsa.c",
-        "third_party/boringssl/src/crypto/dsa/dsa_asn1.c",
-        "third_party/boringssl/src/crypto/ec_extra/ec_asn1.c",
-        "third_party/boringssl/src/crypto/ec_extra/ec_derive.c",
-        "third_party/boringssl/src/crypto/ec_extra/hash_to_curve.c",
-        "third_party/boringssl/src/crypto/ecdh_extra/ecdh_extra.c",
-        "third_party/boringssl/src/crypto/ecdsa_extra/ecdsa_asn1.c",
-        "third_party/boringssl/src/crypto/engine/engine.c",
-        "third_party/boringssl/src/crypto/err/err.c",
-        "third_party/boringssl/src/crypto/evp/evp.c",
-        "third_party/boringssl/src/crypto/evp/evp_asn1.c",
-        "third_party/boringssl/src/crypto/evp/evp_ctx.c",
-        "third_party/boringssl/src/crypto/evp/p_dsa_asn1.c",
-        "third_party/boringssl/src/crypto/evp/p_ec.c",
-        "third_party/boringssl/src/crypto/evp/p_ec_asn1.c",
-        "third_party/boringssl/src/crypto/evp/p_ed25519.c",
-        "third_party/boringssl/src/crypto/evp/p_ed25519_asn1.c",
-        "third_party/boringssl/src/crypto/evp/p_hkdf.c",
-        "third_party/boringssl/src/crypto/evp/p_rsa.c",
-        "third_party/boringssl/src/crypto/evp/p_rsa_asn1.c",
-        "third_party/boringssl/src/crypto/evp/p_x25519.c",
-        "third_party/boringssl/src/crypto/evp/p_x25519_asn1.c",
-        "third_party/boringssl/src/crypto/evp/pbkdf.c",
-        "third_party/boringssl/src/crypto/evp/print.c",
-        "third_party/boringssl/src/crypto/evp/scrypt.c",
-        "third_party/boringssl/src/crypto/evp/sign.c",
-        "third_party/boringssl/src/crypto/ex_data.c",
-        "third_party/boringssl/src/crypto/fipsmodule/bcm.c",
-        "third_party/boringssl/src/crypto/fipsmodule/fips_shared_support.c",
-        "third_party/boringssl/src/crypto/hkdf/hkdf.c",
-        "third_party/boringssl/src/crypto/hpke/hpke.c",
-        "third_party/boringssl/src/crypto/hrss/hrss.c",
-        "third_party/boringssl/src/crypto/lhash/lhash.c",
-        "third_party/boringssl/src/crypto/mem.c",
-        "third_party/boringssl/src/crypto/obj/obj.c",
-        "third_party/boringssl/src/crypto/obj/obj_xref.c",
-        "third_party/boringssl/src/crypto/pem/pem_all.c",
-        "third_party/boringssl/src/crypto/pem/pem_info.c",
-        "third_party/boringssl/src/crypto/pem/pem_lib.c",
-        "third_party/boringssl/src/crypto/pem/pem_oth.c",
-        "third_party/boringssl/src/crypto/pem/pem_pk8.c",
-        "third_party/boringssl/src/crypto/pem/pem_pkey.c",
-        "third_party/boringssl/src/crypto/pem/pem_x509.c",
-        "third_party/boringssl/src/crypto/pem/pem_xaux.c",
-        "third_party/boringssl/src/crypto/pkcs7/pkcs7.c",
-        "third_party/boringssl/src/crypto/pkcs7/pkcs7_x509.c",
-        "third_party/boringssl/src/crypto/pkcs8/p5_pbev2.c",
-        "third_party/boringssl/src/crypto/pkcs8/pkcs8.c",
-        "third_party/boringssl/src/crypto/pkcs8/pkcs8_x509.c",
-        "third_party/boringssl/src/crypto/poly1305/poly1305.c",
-        "third_party/boringssl/src/crypto/poly1305/poly1305_arm.c",
-        "third_party/boringssl/src/crypto/poly1305/poly1305_vec.c",
-        "third_party/boringssl/src/crypto/pool/pool.c",
-        "third_party/boringssl/src/crypto/rand_extra/deterministic.c",
-        "third_party/boringssl/src/crypto/rand_extra/forkunsafe.c",
-        "third_party/boringssl/src/crypto/rand_extra/fuchsia.c",
-        "third_party/boringssl/src/crypto/rand_extra/passive.c",
-        "third_party/boringssl/src/crypto/rand_extra/rand_extra.c",
-        "third_party/boringssl/src/crypto/rand_extra/windows.c",
-        "third_party/boringssl/src/crypto/rc4/rc4.c",
-        "third_party/boringssl/src/crypto/refcount_c11.c",
-        "third_party/boringssl/src/crypto/refcount_lock.c",
-        "third_party/boringssl/src/crypto/rsa_extra/rsa_asn1.c",
-        "third_party/boringssl/src/crypto/rsa_extra/rsa_print.c",
-        "third_party/boringssl/src/crypto/siphash/siphash.c",
-        "third_party/boringssl/src/crypto/stack/stack.c",
-        "third_party/boringssl/src/crypto/thread.c",
-        "third_party/boringssl/src/crypto/thread_none.c",
-        "third_party/boringssl/src/crypto/thread_pthread.c",
-        "third_party/boringssl/src/crypto/thread_win.c",
-        "third_party/boringssl/src/crypto/trust_token/pmbtoken.c",
-        "third_party/boringssl/src/crypto/trust_token/trust_token.c",
-        "third_party/boringssl/src/crypto/trust_token/voprf.c",
-        "third_party/boringssl/src/crypto/x509/a_digest.c",
-        "third_party/boringssl/src/crypto/x509/a_sign.c",
-        "third_party/boringssl/src/crypto/x509/a_verify.c",
-        "third_party/boringssl/src/crypto/x509/algorithm.c",
-        "third_party/boringssl/src/crypto/x509/asn1_gen.c",
-        "third_party/boringssl/src/crypto/x509/by_dir.c",
-        "third_party/boringssl/src/crypto/x509/by_file.c",
-        "third_party/boringssl/src/crypto/x509/i2d_pr.c",
-        "third_party/boringssl/src/crypto/x509/name_print.c",
-        "third_party/boringssl/src/crypto/x509/rsa_pss.c",
-        "third_party/boringssl/src/crypto/x509/t_crl.c",
-        "third_party/boringssl/src/crypto/x509/t_req.c",
-        "third_party/boringssl/src/crypto/x509/t_x509.c",
-        "third_party/boringssl/src/crypto/x509/t_x509a.c",
-        "third_party/boringssl/src/crypto/x509/x509.c",
-        "third_party/boringssl/src/crypto/x509/x509_att.c",
-        "third_party/boringssl/src/crypto/x509/x509_cmp.c",
-        "third_party/boringssl/src/crypto/x509/x509_d2.c",
-        "third_party/boringssl/src/crypto/x509/x509_def.c",
-        "third_party/boringssl/src/crypto/x509/x509_ext.c",
-        "third_party/boringssl/src/crypto/x509/x509_lu.c",
-        "third_party/boringssl/src/crypto/x509/x509_obj.c",
-        "third_party/boringssl/src/crypto/x509/x509_req.c",
-        "third_party/boringssl/src/crypto/x509/x509_set.c",
-        "third_party/boringssl/src/crypto/x509/x509_trs.c",
-        "third_party/boringssl/src/crypto/x509/x509_txt.c",
-        "third_party/boringssl/src/crypto/x509/x509_v3.c",
-        "third_party/boringssl/src/crypto/x509/x509_vfy.c",
-        "third_party/boringssl/src/crypto/x509/x509_vpm.c",
-        "third_party/boringssl/src/crypto/x509/x509cset.c",
-        "third_party/boringssl/src/crypto/x509/x509name.c",
-        "third_party/boringssl/src/crypto/x509/x509rset.c",
-        "third_party/boringssl/src/crypto/x509/x509spki.c",
-        "third_party/boringssl/src/crypto/x509/x_algor.c",
-        "third_party/boringssl/src/crypto/x509/x_all.c",
-        "third_party/boringssl/src/crypto/x509/x_attrib.c",
-        "third_party/boringssl/src/crypto/x509/x_crl.c",
-        "third_party/boringssl/src/crypto/x509/x_exten.c",
-        "third_party/boringssl/src/crypto/x509/x_info.c",
-        "third_party/boringssl/src/crypto/x509/x_name.c",
-        "third_party/boringssl/src/crypto/x509/x_pkey.c",
-        "third_party/boringssl/src/crypto/x509/x_pubkey.c",
-        "third_party/boringssl/src/crypto/x509/x_req.c",
-        "third_party/boringssl/src/crypto/x509/x_sig.c",
-        "third_party/boringssl/src/crypto/x509/x_spki.c",
-        "third_party/boringssl/src/crypto/x509/x_val.c",
-        "third_party/boringssl/src/crypto/x509/x_x509.c",
-        "third_party/boringssl/src/crypto/x509/x_x509a.c",
-        "third_party/boringssl/src/crypto/x509v3/pcy_cache.c",
-        "third_party/boringssl/src/crypto/x509v3/pcy_data.c",
-        "third_party/boringssl/src/crypto/x509v3/pcy_map.c",
-        "third_party/boringssl/src/crypto/x509v3/pcy_node.c",
-        "third_party/boringssl/src/crypto/x509v3/pcy_tree.c",
-        "third_party/boringssl/src/crypto/x509v3/v3_akey.c",
-        "third_party/boringssl/src/crypto/x509v3/v3_akeya.c",
-        "third_party/boringssl/src/crypto/x509v3/v3_alt.c",
-        "third_party/boringssl/src/crypto/x509v3/v3_bcons.c",
-        "third_party/boringssl/src/crypto/x509v3/v3_bitst.c",
-        "third_party/boringssl/src/crypto/x509v3/v3_conf.c",
-        "third_party/boringssl/src/crypto/x509v3/v3_cpols.c",
-        "third_party/boringssl/src/crypto/x509v3/v3_crld.c",
-        "third_party/boringssl/src/crypto/x509v3/v3_enum.c",
-        "third_party/boringssl/src/crypto/x509v3/v3_extku.c",
-        "third_party/boringssl/src/crypto/x509v3/v3_genn.c",
-        "third_party/boringssl/src/crypto/x509v3/v3_ia5.c",
-        "third_party/boringssl/src/crypto/x509v3/v3_info.c",
-        "third_party/boringssl/src/crypto/x509v3/v3_int.c",
-        "third_party/boringssl/src/crypto/x509v3/v3_lib.c",
-        "third_party/boringssl/src/crypto/x509v3/v3_ncons.c",
-        "third_party/boringssl/src/crypto/x509v3/v3_ocsp.c",
-        "third_party/boringssl/src/crypto/x509v3/v3_pci.c",
-        "third_party/boringssl/src/crypto/x509v3/v3_pcia.c",
-        "third_party/boringssl/src/crypto/x509v3/v3_pcons.c",
-        "third_party/boringssl/src/crypto/x509v3/v3_pmaps.c",
-        "third_party/boringssl/src/crypto/x509v3/v3_prn.c",
-        "third_party/boringssl/src/crypto/x509v3/v3_purp.c",
-        "third_party/boringssl/src/crypto/x509v3/v3_skey.c",
-        "third_party/boringssl/src/crypto/x509v3/v3_utl.c",
-        "third_party/boringssl/src/ssl/bio_ssl.cc",
-        "third_party/boringssl/src/ssl/d1_both.cc",
-        "third_party/boringssl/src/ssl/d1_lib.cc",
-        "third_party/boringssl/src/ssl/d1_pkt.cc",
-        "third_party/boringssl/src/ssl/d1_srtp.cc",
-        "third_party/boringssl/src/ssl/dtls_method.cc",
-        "third_party/boringssl/src/ssl/dtls_record.cc",
-        "third_party/boringssl/src/ssl/encrypted_client_hello.cc",
-        "third_party/boringssl/src/ssl/extensions.cc",
-        "third_party/boringssl/src/ssl/handoff.cc",
-        "third_party/boringssl/src/ssl/handshake.cc",
-        "third_party/boringssl/src/ssl/handshake_client.cc",
-        "third_party/boringssl/src/ssl/handshake_server.cc",
-        "third_party/boringssl/src/ssl/s3_both.cc",
-        "third_party/boringssl/src/ssl/s3_lib.cc",
-        "third_party/boringssl/src/ssl/s3_pkt.cc",
-        "third_party/boringssl/src/ssl/ssl_aead_ctx.cc",
-        "third_party/boringssl/src/ssl/ssl_asn1.cc",
-        "third_party/boringssl/src/ssl/ssl_buffer.cc",
-        "third_party/boringssl/src/ssl/ssl_cert.cc",
-        "third_party/boringssl/src/ssl/ssl_cipher.cc",
-        "third_party/boringssl/src/ssl/ssl_file.cc",
-        "third_party/boringssl/src/ssl/ssl_key_share.cc",
-        "third_party/boringssl/src/ssl/ssl_lib.cc",
-        "third_party/boringssl/src/ssl/ssl_privkey.cc",
-        "third_party/boringssl/src/ssl/ssl_session.cc",
-        "third_party/boringssl/src/ssl/ssl_stat.cc",
-        "third_party/boringssl/src/ssl/ssl_transcript.cc",
-        "third_party/boringssl/src/ssl/ssl_versions.cc",
-        "third_party/boringssl/src/ssl/ssl_x509.cc",
-        "third_party/boringssl/src/ssl/t1_enc.cc",
-        "third_party/boringssl/src/ssl/tls13_both.cc",
-        "third_party/boringssl/src/ssl/tls13_client.cc",
-        "third_party/boringssl/src/ssl/tls13_enc.cc",
-        "third_party/boringssl/src/ssl/tls13_server.cc",
-        "third_party/boringssl/src/ssl/tls_method.cc",
-        "third_party/boringssl/src/ssl/tls_record.cc",
-    ],
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DANDROID",
-        "-DANDROID_NDK_VERSION_ROLL=r23_1",
-        "-DBORINGSSL_ALLOW_CXX_RUNTIME",
-        "-DBORINGSSL_IMPLEMENTATION",
-        "-DBORINGSSL_NO_STATIC_INITIALIZER",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-6578-g0d30e92f-2\"",
-        "-DCR_LIBCXX_REVISION=64d36e572d3f9719c5d75011a718f33f11126851",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=0",
-        "-DHAVE_SYS_UIO_H",
-        "-DNDEBUG",
-        "-DNO_UNWIND_TABLES",
-        "-DNVALGRIND",
-        "-DOFFICIAL_BUILD",
-        "-DOPENSSL_SMALL",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-Oz",
-        "-fdata-sections",
-        "-ffunction-sections",
-        "-fno-asynchronous-unwind-tables",
-        "-fno-unwind-tables",
-        "-fvisibility-inlines-hidden",
-        "-fvisibility=hidden",
-        "-g1",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/boringssl/src/include/",
-    ],
-    cpp_std: "c++17",
-    ldflags: [
-        "-Wl,--as-needed",
-        "-Wl,--gc-sections",
-        "-Wl,--icf=all",
-    ],
-    target: {
-        android_arm: {
-            cflags: [
-                "-fstack-protector",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-fstack-protector",
-                "-mno-outline",
-                "-mno-outline-atomics",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-fstack-protector",
-                "-msse3",
-            ],
-        },
-    },
-}
-
 // GN: //third_party/boringssl:boringssl__testing
 cc_library_static {
     name: "cronet_aml_third_party_boringssl_boringssl__testing",
@@ -26338,140 +28165,6 @@
     },
 }
 
-// GN: //third_party/boringssl:boringssl_asm
-cc_object {
-    name: "cronet_aml_third_party_boringssl_boringssl_asm",
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DANDROID",
-        "-DANDROID_NDK_VERSION_ROLL=r23_1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-6578-g0d30e92f-2\"",
-        "-DCR_LIBCXX_REVISION=64d36e572d3f9719c5d75011a718f33f11126851",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=0",
-        "-DHAVE_SYS_UIO_H",
-        "-DNDEBUG",
-        "-DNO_UNWIND_TABLES",
-        "-DNVALGRIND",
-        "-DOFFICIAL_BUILD",
-        "-D_FORTIFY_SOURCE=2",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D__STDC_CONSTANT_MACROS",
-        "-D__STDC_FORMAT_MACROS",
-        "-Oz",
-        "-fdata-sections",
-        "-ffunction-sections",
-        "-fno-asynchronous-unwind-tables",
-        "-fno-unwind-tables",
-        "-fvisibility-inlines-hidden",
-        "-fvisibility=hidden",
-        "-g1",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/boringssl/src/include/",
-    ],
-    cpp_std: "c++17",
-    target: {
-        android_arm: {
-            srcs: [
-                "third_party/boringssl/linux-arm/crypto/chacha/chacha-armv4.S",
-                "third_party/boringssl/linux-arm/crypto/fipsmodule/aesv8-armx32.S",
-                "third_party/boringssl/linux-arm/crypto/fipsmodule/armv4-mont.S",
-                "third_party/boringssl/linux-arm/crypto/fipsmodule/bsaes-armv7.S",
-                "third_party/boringssl/linux-arm/crypto/fipsmodule/ghash-armv4.S",
-                "third_party/boringssl/linux-arm/crypto/fipsmodule/ghashv8-armx32.S",
-                "third_party/boringssl/linux-arm/crypto/fipsmodule/sha1-armv4-large.S",
-                "third_party/boringssl/linux-arm/crypto/fipsmodule/sha256-armv4.S",
-                "third_party/boringssl/linux-arm/crypto/fipsmodule/sha512-armv4.S",
-                "third_party/boringssl/linux-arm/crypto/fipsmodule/vpaes-armv7.S",
-                "third_party/boringssl/linux-arm/crypto/test/trampoline-armv4.S",
-                "third_party/boringssl/src/crypto/curve25519/asm/x25519-asm-arm.S",
-                "third_party/boringssl/src/crypto/poly1305/poly1305_arm_asm.S",
-            ],
-            cflags: [
-                "-fstack-protector",
-            ],
-        },
-        android_arm64: {
-            srcs: [
-                "third_party/boringssl/linux-aarch64/crypto/chacha/chacha-armv8.S",
-                "third_party/boringssl/linux-aarch64/crypto/cipher_extra/chacha20_poly1305_armv8.S",
-                "third_party/boringssl/linux-aarch64/crypto/fipsmodule/aesv8-armx64.S",
-                "third_party/boringssl/linux-aarch64/crypto/fipsmodule/armv8-mont.S",
-                "third_party/boringssl/linux-aarch64/crypto/fipsmodule/ghash-neon-armv8.S",
-                "third_party/boringssl/linux-aarch64/crypto/fipsmodule/ghashv8-armx64.S",
-                "third_party/boringssl/linux-aarch64/crypto/fipsmodule/p256-armv8-asm.S",
-                "third_party/boringssl/linux-aarch64/crypto/fipsmodule/p256_beeu-armv8-asm.S",
-                "third_party/boringssl/linux-aarch64/crypto/fipsmodule/sha1-armv8.S",
-                "third_party/boringssl/linux-aarch64/crypto/fipsmodule/sha256-armv8.S",
-                "third_party/boringssl/linux-aarch64/crypto/fipsmodule/sha512-armv8.S",
-                "third_party/boringssl/linux-aarch64/crypto/fipsmodule/vpaes-armv8.S",
-                "third_party/boringssl/linux-aarch64/crypto/test/trampoline-armv8.S",
-            ],
-            cflags: [
-                "-fstack-protector",
-                "-mno-outline",
-                "-mno-outline-atomics",
-            ],
-        },
-        android_x86: {
-            srcs: [
-                "third_party/boringssl/linux-x86/crypto/chacha/chacha-x86.S",
-                "third_party/boringssl/linux-x86/crypto/fipsmodule/aesni-x86.S",
-                "third_party/boringssl/linux-x86/crypto/fipsmodule/bn-586.S",
-                "third_party/boringssl/linux-x86/crypto/fipsmodule/co-586.S",
-                "third_party/boringssl/linux-x86/crypto/fipsmodule/ghash-ssse3-x86.S",
-                "third_party/boringssl/linux-x86/crypto/fipsmodule/ghash-x86.S",
-                "third_party/boringssl/linux-x86/crypto/fipsmodule/md5-586.S",
-                "third_party/boringssl/linux-x86/crypto/fipsmodule/sha1-586.S",
-                "third_party/boringssl/linux-x86/crypto/fipsmodule/sha256-586.S",
-                "third_party/boringssl/linux-x86/crypto/fipsmodule/sha512-586.S",
-                "third_party/boringssl/linux-x86/crypto/fipsmodule/vpaes-x86.S",
-                "third_party/boringssl/linux-x86/crypto/fipsmodule/x86-mont.S",
-                "third_party/boringssl/linux-x86/crypto/test/trampoline-x86.S",
-            ],
-            cflags: [
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            srcs: [
-                "third_party/boringssl/linux-x86_64/crypto/chacha/chacha-x86_64.S",
-                "third_party/boringssl/linux-x86_64/crypto/cipher_extra/aes128gcmsiv-x86_64.S",
-                "third_party/boringssl/linux-x86_64/crypto/cipher_extra/chacha20_poly1305_x86_64.S",
-                "third_party/boringssl/linux-x86_64/crypto/fipsmodule/aesni-gcm-x86_64.S",
-                "third_party/boringssl/linux-x86_64/crypto/fipsmodule/aesni-x86_64.S",
-                "third_party/boringssl/linux-x86_64/crypto/fipsmodule/ghash-ssse3-x86_64.S",
-                "third_party/boringssl/linux-x86_64/crypto/fipsmodule/ghash-x86_64.S",
-                "third_party/boringssl/linux-x86_64/crypto/fipsmodule/md5-x86_64.S",
-                "third_party/boringssl/linux-x86_64/crypto/fipsmodule/p256-x86_64-asm.S",
-                "third_party/boringssl/linux-x86_64/crypto/fipsmodule/p256_beeu-x86_64-asm.S",
-                "third_party/boringssl/linux-x86_64/crypto/fipsmodule/rdrand-x86_64.S",
-                "third_party/boringssl/linux-x86_64/crypto/fipsmodule/rsaz-avx2.S",
-                "third_party/boringssl/linux-x86_64/crypto/fipsmodule/sha1-x86_64.S",
-                "third_party/boringssl/linux-x86_64/crypto/fipsmodule/sha256-x86_64.S",
-                "third_party/boringssl/linux-x86_64/crypto/fipsmodule/sha512-x86_64.S",
-                "third_party/boringssl/linux-x86_64/crypto/fipsmodule/vpaes-x86_64.S",
-                "third_party/boringssl/linux-x86_64/crypto/fipsmodule/x86_64-mont.S",
-                "third_party/boringssl/linux-x86_64/crypto/fipsmodule/x86_64-mont5.S",
-                "third_party/boringssl/linux-x86_64/crypto/test/trampoline-x86_64.S",
-                "third_party/boringssl/src/crypto/hrss/asm/poly_rq_mul.S",
-            ],
-            cflags: [
-                "-fstack-protector",
-                "-msse3",
-            ],
-        },
-    },
-}
-
 // GN: //third_party/boringssl:boringssl_asm__testing
 cc_object {
     name: "cronet_aml_third_party_boringssl_boringssl_asm__testing",
@@ -30092,6 +31785,48 @@
     ],
 }
 
+// GN: //third_party/quic_trace:quic_trace_proto__testing
+cc_genrule {
+    name: "cronet_aml_third_party_quic_trace_quic_trace_proto__testing_gen",
+    srcs: [
+        "third_party/quic_trace/src/quic_trace/quic_trace.proto",
+    ],
+    tools: [
+        "cronet_aml_third_party_protobuf_protoc",
+    ],
+    cmd: "$(location cronet_aml_third_party_protobuf_protoc) --proto_path=external/cronet/third_party/quic_trace/src/quic_trace --cpp_out=lite=true:$(genDir)/external/cronet/third_party/quic_trace/src/quic_trace/ $(in)",
+    out: [
+        "external/cronet/third_party/quic_trace/src/quic_trace/quic_trace.pb.cc",
+    ],
+    apex_available: [
+        "com.android.tethering",
+    ],
+}
+
+// GN: //third_party/quic_trace:quic_trace_proto__testing
+cc_genrule {
+    name: "cronet_aml_third_party_quic_trace_quic_trace_proto__testing_gen_headers",
+    srcs: [
+        "third_party/quic_trace/src/quic_trace/quic_trace.proto",
+    ],
+    tools: [
+        "cronet_aml_third_party_protobuf_protoc",
+    ],
+    cmd: "$(location cronet_aml_third_party_protobuf_protoc) --proto_path=external/cronet/third_party/quic_trace/src/quic_trace --cpp_out=lite=true:$(genDir)/external/cronet/third_party/quic_trace/src/quic_trace/ $(in)",
+    out: [
+        "external/cronet/third_party/quic_trace/src/quic_trace/quic_trace.pb.h",
+    ],
+    export_include_dirs: [
+        ".",
+        "protos",
+        "third_party/quic_trace/src",
+        "third_party/quic_trace/src/quic_trace",
+    ],
+    apex_available: [
+        "com.android.tethering",
+    ],
+}
+
 // GN: //url:buildflags
 cc_genrule {
     name: "cronet_aml_url_buildflags",
@@ -30166,6 +31901,8 @@
         "url/url_util.cc",
     ],
     shared_libs: [
+        "//external/cronet/third_party/boringssl:libcrypto",
+        "//external/cronet/third_party/boringssl:libssl",
         "libandroid",
         "liblog",
     ],
@@ -30175,7 +31912,6 @@
         "cronet_aml_base_base_static",
         "cronet_aml_base_third_party_double_conversion_double_conversion",
         "cronet_aml_base_third_party_dynamic_annotations_dynamic_annotations",
-        "cronet_aml_third_party_boringssl_boringssl",
         "cronet_aml_third_party_icu_icui18n",
         "cronet_aml_third_party_icu_icuuc_private",
         "cronet_aml_third_party_libevent_libevent",
@@ -30504,51 +32240,3 @@
     ],
 }
 
-// GN: LICENSE
-license {
-    name: "external_cronet_license",
-    license_kinds: [
-        "SPDX-license-identifier-AFL-2.0",
-        "SPDX-license-identifier-Apache-2.0",
-        "SPDX-license-identifier-BSD",
-        "SPDX-license-identifier-BSL-1.0",
-        "SPDX-license-identifier-ICU",
-        "SPDX-license-identifier-ISC",
-        "SPDX-license-identifier-MIT",
-        "SPDX-license-identifier-MPL",
-        "SPDX-license-identifier-MPL-1.1",
-        "SPDX-license-identifier-MPL-2.0",
-        "SPDX-license-identifier-NCSA",
-        "SPDX-license-identifier-OpenSSL",
-        "SPDX-license-identifier-Unicode-DFS",
-        "legacy_unencumbered",
-    ],
-    license_text: [
-        "LICENSE",
-        "base/third_party/double_conversion/LICENSE",
-        "base/third_party/dynamic_annotations/LICENSE",
-        "base/third_party/icu/LICENSE",
-        "base/third_party/nspr/LICENSE",
-        "base/third_party/superfasthash/LICENSE",
-        "base/third_party/symbolize/LICENSE",
-        "base/third_party/valgrind/LICENSE",
-        "base/third_party/xdg_user_dirs/LICENSE",
-        "net/third_party/quiche/src/LICENSE",
-        "net/third_party/uri_template/LICENSE",
-        "third_party/abseil-cpp/LICENSE",
-        "third_party/ashmem/LICENSE",
-        "third_party/boringssl/src/LICENSE",
-        "third_party/boringssl/src/third_party/fiat/LICENSE",
-        "third_party/boringssl/src/third_party/googletest/LICENSE",
-        "third_party/boringssl/src/third_party/wycheproof_testvectors/LICENSE",
-        "third_party/brotli/LICENSE",
-        "third_party/icu/LICENSE",
-        "third_party/icu/scripts/LICENSE",
-        "third_party/libevent/LICENSE",
-        "third_party/metrics_proto/LICENSE",
-        "third_party/modp_b64/LICENSE",
-        "third_party/protobuf/LICENSE",
-        "third_party/protobuf/third_party/utf8_range/LICENSE",
-    ],
-}
-
diff --git a/tools/gn2bp/desc_arm.json b/tools/gn2bp/desc_arm.json
index 87b86e3..8087bc9 100644
--- a/tools/gn2bp/desc_arm.json
+++ b/tools/gn2bp/desc_arm.json
Binary files differ
diff --git a/tools/gn2bp/desc_arm64.json b/tools/gn2bp/desc_arm64.json
index 7b8bfe3..99e2332 100644
--- a/tools/gn2bp/desc_arm64.json
+++ b/tools/gn2bp/desc_arm64.json
Binary files differ
diff --git a/tools/gn2bp/desc_x64.json b/tools/gn2bp/desc_x64.json
index df8635a..bba6b56 100644
--- a/tools/gn2bp/desc_x64.json
+++ b/tools/gn2bp/desc_x64.json
Binary files differ
diff --git a/tools/gn2bp/desc_x86.json b/tools/gn2bp/desc_x86.json
index 707b9c7..a5d4891 100644
--- a/tools/gn2bp/desc_x86.json
+++ b/tools/gn2bp/desc_x86.json
Binary files differ
diff --git a/tools/gn2bp/gen_android_bp b/tools/gn2bp/gen_android_bp
index 2c970a5..b55b9bf 100755
--- a/tools/gn2bp/gen_android_bp
+++ b/tools/gn2bp/gen_android_bp
@@ -39,6 +39,8 @@
 
 ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
 
+CRONET_LICENSE_NAME = "external_cronet_license"
+
 # Default targets to translate to the blueprint file.
 DEFAULT_TARGETS = [
     '//components/cronet/android:cronet',
@@ -47,6 +49,7 @@
 
 DEFAULT_TESTS = [
   '//components/cronet/android:cronet_unittests_android__library',
+  '//net:net_unittests__library',
 ]
 
 EXTRAS_ANDROID_BP_FILE = "Android.extras.bp"
@@ -164,6 +167,21 @@
 
 # Additional arguments to apply to Android.bp rules.
 additional_args = {
+    'cronet_aml_net_third_party_quiche_net_quic_test_tools_proto_gen_headers': [
+      ('export_include_dirs', {
+        "net/third_party/quiche/src",
+      })
+    ],
+    'cronet_aml_net_third_party_quiche_net_quic_test_tools_proto__testing_gen_headers': [
+        ('export_include_dirs', {
+        "net/third_party/quiche/src",
+        })
+     ],
+    'cronet_aml_third_party_quic_trace_quic_trace_proto__testing_gen_headers': [
+        ('export_include_dirs', {
+          "third_party/quic_trace/src",
+        })
+    ],
     'cronet_aml_net_net': [
         ('export_static_lib_headers', {
             'cronet_aml_net_third_party_quiche_quiche',
@@ -188,6 +206,9 @@
     ],
 }
 
+def always_disable(module, arch):
+  return None
+
 def enable_brotli(module, arch):
   # Requires crrev/c/4111690
   if arch is None:
@@ -213,25 +234,40 @@
   else:
     module.arch[arch].shared_libs.add('libz')
 
+def enable_boringssl(module, arch):
+  if arch is None:
+    shared_libs = module.shared_libs
+  else:
+    shared_libs = module.arch[arch].shared_libs
+  shared_libs.add('//external/cronet/third_party/boringssl:libcrypto')
+  shared_libs.add('//external/cronet/third_party/boringssl:libssl')
+
 # Android equivalents for third-party libraries that the upstream project
 # depends on.
 builtin_deps = {
     '//buildtools/third_party/libunwind:libunwind':
-        lambda m, a: None, # disable libunwind
+        always_disable,
     '//buildtools/third_party/libunwind:libunwind__testing':
-        lambda m, a: None, # disable libunwind
+        always_disable,
     '//net/data/ssl/chrome_root_store:gen_root_store_inc':
-        lambda m, a: None,
+        always_disable,
     '//net/data/ssl/chrome_root_store:gen_root_store_inc__testing':
-        lambda m, a: None,
+        always_disable,
     '//net/tools/root_store_tool:root_store_tool':
-        lambda m, a: None,
+        always_disable,
     '//net/tools/root_store_tool:root_store_tool__testing':
-        lambda m, a: None,
+        always_disable,
     '//third_party/zlib:zlib':
         enable_zlib,
     '//third_party/zlib:zlib__testing':
         enable_zlib,
+    '//third_party/boringssl:boringssl':
+        enable_boringssl,
+    '//third_party/boringssl:boringssl_asm':
+        # Due to FIPS requirements, downstream BoringSSL has a different "shape" than upstream's.
+        # We're guaranteed that if X depends on :boringssl it will also depend on :boringssl_asm.
+        # Hence, always drop :boringssl_asm and handle the translation entirely in :boringssl.
+        always_disable,
 }
 
 experimental_android_deps = {
@@ -1020,10 +1056,19 @@
       return "$(location %s)" % arg.replace("gen/", "")
     return arg
 
+  def _replace_binary(self, arg):
+    if arg in self.binary_to_target:
+      return '$(location %s)' % self.binary
+    return arg
+
+  def _remove_python_args(self):
+    self.target.args = [arg for arg in self.target.args if "python3" not in arg]
+
   def _sanitize_args(self):
     self._update_all_args(self._sanitize_filepath_with_location_tag)
     self._update_all_args(self._replace_gen_with_location_tag)
-    self._set_arg_at(0, '$(location %s)' % self.binary)
+    self._update_all_args(self._replace_binary)
+    self._remove_python_args()
     super()._sanitize_args()
 
   def get_tools(self):
@@ -1217,7 +1262,8 @@
     # don't add script arg for the first source -- create_action_module
     # already does this.
     if i != 0:
-      new_args.append('&& python3 $(location %s)' %
+      new_args.append('&&')
+      new_args.append('python3 $(location %s)' %
                    gn_utils.label_to_path(target.script))
     for arg in target.args:
       if '{{source}}' in arg:
@@ -1231,6 +1277,10 @@
         for out in target.outputs:
           if out.endswith(file_name):
             new_args.append('$(location %s)' % out)
+
+        for file in target.sources:
+          if file.endswith(file_name):
+            new_args.append('$(location %s)' % gn_utils.label_to_path(file))
       else:
         new_args.append(arg)
 
@@ -1774,54 +1824,10 @@
 
   return blueprint
 
-def create_license_module(blueprint):
-  module = Module("license", "external_cronet_license", "LICENSE")
-  module.license_kinds.update({
-      'SPDX-license-identifier-MPL',
-      'SPDX-license-identifier-MPL-1.1',
-      'SPDX-license-identifier-ISC',
-      'SPDX-license-identifier-AFL-2.0',
-      'SPDX-license-identifier-MPL-2.0',
-      'SPDX-license-identifier-BSD',
-      'SPDX-license-identifier-Apache-2.0',
-      'SPDX-license-identifier-BSL-1.0',
-      'SPDX-license-identifier-Unicode-DFS',
-      'SPDX-license-identifier-NCSA',
-      'SPDX-license-identifier-OpenSSL',
-      'SPDX-license-identifier-MIT',
-      "SPDX-license-identifier-ICU",
-      'legacy_unencumbered',
-  })
-  module.license_text.update({
-      "LICENSE",
-      "net/third_party/uri_template/LICENSE",
-      "net/third_party/quiche/src/LICENSE",
-      "base/third_party/symbolize/LICENSE",
-      "base/third_party/superfasthash/LICENSE",
-      "base/third_party/xdg_user_dirs/LICENSE",
-      "base/third_party/double_conversion/LICENSE",
-      "base/third_party/nspr/LICENSE",
-      "base/third_party/dynamic_annotations/LICENSE",
-      "base/third_party/icu/LICENSE",
-      "base/third_party/valgrind/LICENSE",
-      "third_party/brotli/LICENSE",
-      "third_party/protobuf/LICENSE",
-      "third_party/protobuf/third_party/utf8_range/LICENSE",
-      "third_party/metrics_proto/LICENSE",
-      "third_party/boringssl/src/LICENSE",
-      "third_party/boringssl/src/third_party/googletest/LICENSE",
-      "third_party/boringssl/src/third_party/wycheproof_testvectors/LICENSE",
-      "third_party/boringssl/src/third_party/fiat/LICENSE",
-      "third_party/libevent/LICENSE",
-      "third_party/ashmem/LICENSE",
-      "third_party/icu/LICENSE",
-      "third_party/icu/scripts/LICENSE",
-      "third_party/abseil-cpp/LICENSE",
-      "third_party/modp_b64/LICENSE",
-  })
+def create_default_license_module(blueprint):
   default_license = Module("package", "", "PACKAGE")
-  default_license.default_applicable_licenses.add(module.name)
-  blueprint.add_module(module)
+  default_license.comment = "The actual license can be found in Android.extras.bp"
+  default_license.default_applicable_licenses.add(CRONET_LICENSE_NAME)
   blueprint.add_module(default_license)
 
 def main():
@@ -1876,7 +1882,7 @@
   # Add any proto groups to the blueprint.
   for l_name, t_names in proto_groups.items():
     create_proto_group_modules(blueprint, gn, l_name, t_names)
-  create_license_module(blueprint)
+  create_default_license_module(blueprint)
   output = [
       """// Copyright (C) 2022 The Android Open Source Project
 //