Merge "Add comment for per-app preference priority" into main
diff --git a/Cronet/tests/OWNERS b/Cronet/tests/OWNERS
deleted file mode 100644
index a35a789..0000000
--- a/Cronet/tests/OWNERS
+++ /dev/null
@@ -1,8 +0,0 @@
-# Bug component: 31808
-
-set noparent
-file:platform/packages/modules/Connectivity:main:/OWNERS_core_networking_xts
-
-# TODO: Temp ownership to develop cronet CTS
-colibie@google.com #{LAST_RESORT_SUGGESTION}
-prohr@google.com #{LAST_RESORT_SUGGESTION}
diff --git a/Cronet/tests/common/Android.bp b/Cronet/tests/common/Android.bp
deleted file mode 100644
index 703f544..0000000
--- a/Cronet/tests/common/Android.bp
+++ /dev/null
@@ -1,49 +0,0 @@
-// 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.
-//
-
-// Tests in this folder are included both in unit tests and CTS.
-// They must be fast and stable, and exercise public or test APIs.
-
-package {
-    default_team: "trendy_team_fwk_core_networking",
-    // See: http://go/android-license-faq
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-// TODO: Consider merging with ConnectivityCoverageTests which is a collection of all
-// Connectivity tests being used for coverage. This will depend on how far we decide to
-// go with merging NetHttp and Tethering targets.
-android_test {
-    name: "NetHttpCoverageTests",
-    enforce_default_target_sdk_version: true,
-    min_sdk_version: "30",
-    test_suites: [
-        "general-tests",
-        "mts-tethering",
-    ],
-    static_libs: [
-        "modules-utils-native-coverage-listener",
-        "CtsNetHttpTestsLib",
-        "NetHttpTestsLibPreJarJar",
-    ],
-    jarjar_rules: ":net-http-test-jarjar-rules",
-    compile_multilib: "both", // Include both the 32 and 64 bit versions
-    jni_libs: [
-        "cronet_aml_components_cronet_android_cronet_tests__testing",
-        "cronet_aml_third_party_netty_tcnative_netty_tcnative_so__testing",
-        "libnativecoverage",
-    ],
-    data: [":cronet_javatests_resources"],
-}
diff --git a/Cronet/tests/common/AndroidManifest.xml b/Cronet/tests/common/AndroidManifest.xml
deleted file mode 100644
index b00fc90..0000000
--- a/Cronet/tests/common/AndroidManifest.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-<?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"
-          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"
-                 android:label="NetHttp coverage tests">
-        <uses-library android:name="android.test.runner" />
-    </application>
-    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="com.android.net.http.tests.coverage"
-                     android:label="NetHttp coverage tests">
-    </instrumentation>
-</manifest>
diff --git a/Cronet/tests/common/AndroidTest.xml b/Cronet/tests/common/AndroidTest.xml
deleted file mode 100644
index 7646a04..0000000
--- a/Cronet/tests/common/AndroidTest.xml
+++ /dev/null
@@ -1,55 +0,0 @@
-<!--
-  ~ 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.
-  -->
-<configuration description="Runs coverage tests for NetHttp">
-    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
-        <option name="test-file-name" value="NetHttpCoverageTests.apk" />
-        <option name="install-arg" value="-t" />
-    </target_preparer>
-    <option name="test-tag" value="NetHttpCoverageTests" />
-
-    <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
-        <option name="push-file" key="net" value="/storage/emulated/0/chromium_tests_root/net" />
-        <option name="push-file" key="test_server" value="/storage/emulated/0/chromium_tests_root/components/cronet/testing/test_server" />
-    </target_preparer>
-    <!-- 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.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" />
-        <!-- b/298380508 -->
-        <option name="exclude-filter" value="org.chromium.net.CronetUrlRequestContextTest#testSetLibraryLoaderIsEnforcedByDefaultEmbeddedProvider" />
-        <!-- b/316559294 -->
-        <option name="exclude-filter" value="org.chromium.net.NQETest#testQuicDisabled" />
-        <!-- b/316559294 -->
-        <option name="exclude-filter" value="org.chromium.net.NQETest#testPrefsWriteRead" />
-        <!-- b/316554711-->
-        <option name="exclude-filter" value="org.chromium.net.NetworkChangesTest" />
-        <!-- b/316550794 -->
-        <option name="exclude-filter" value="org.chromium.net.impl.CronetLoggerTest#testEngineCreation" />
-        <!-- b/327182569 -->
-        <option name="exclude-filter" value="org.chromium.net.urlconnection.CronetURLStreamHandlerFactoryTest#testSetUrlStreamFactoryUsesCronetForNative" />
-        <option name="hidden-api-checks" value="false"/>
-        <option name="isolated-storage" value="false"/>
-        <option name="orchestrator" value="true"/>
-        <option
-            name="device-listeners"
-            value="com.android.modules.utils.testing.NativeCoverageHackInstrumentationListener" />
-    </test>
-</configuration>
diff --git a/Cronet/tests/cts/Android.bp b/Cronet/tests/cts/Android.bp
deleted file mode 100644
index 92b73d9..0000000
--- a/Cronet/tests/cts/Android.bp
+++ /dev/null
@@ -1,71 +0,0 @@
-//
-// Copyright (C) 2019 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_team: "trendy_team_fwk_core_networking",
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-android_library {
-    name: "CtsNetHttpTestsLib",
-    defaults: [
-        "cts_defaults",
-    ],
-    sdk_version: "test_current",
-    min_sdk_version: "30",
-    srcs: [
-        "src/**/*.java",
-        "src/**/*.kt",
-    ],
-    static_libs: [
-        "androidx.test.ext.junit",
-        "ctstestrunner-axt",
-        "ctstestserver",
-        "hamcrest-library",
-        "junit",
-        "kotlin-test",
-        "mockito-target",
-        "net-tests-utils",
-        "truth",
-    ],
-    libs: [
-        "android.test.base",
-        "androidx.annotation_annotation",
-        "framework-connectivity",
-        "org.apache.http.legacy",
-    ],
-    lint: {
-        test: true,
-    },
-}
-
-android_test {
-    name: "CtsNetHttpTestCases",
-    defaults: [
-        "cts_defaults",
-    ],
-    enforce_default_target_sdk_version: true,
-    sdk_version: "test_current",
-    min_sdk_version: "30",
-    static_libs: ["CtsNetHttpTestsLib"],
-    // Tag this as a cts test artifact
-    test_suites: [
-        "cts",
-        "general-tests",
-        "mts-tethering",
-        "mcts-tethering",
-    ],
-}
diff --git a/Cronet/tests/cts/AndroidManifest.xml b/Cronet/tests/cts/AndroidManifest.xml
deleted file mode 100644
index 26900b2..0000000
--- a/Cronet/tests/cts/AndroidManifest.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
- * Copyright (C) 2019 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="android.net.http.cts">
-
-    <uses-permission android:name="android.permission.INTERNET"/>
-    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
-
-    <application android:networkSecurityConfig="@xml/network_security_config">
-        <uses-library android:name="android.test.runner"/>
-    </application>
-
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="android.net.http.cts"
-        android:label="CTS tests of android.net.http">
-    </instrumentation>
-</manifest>
diff --git a/Cronet/tests/cts/AndroidTest.xml b/Cronet/tests/cts/AndroidTest.xml
deleted file mode 100644
index e0421fd..0000000
--- a/Cronet/tests/cts/AndroidTest.xml
+++ /dev/null
@@ -1,38 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2019 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.
-  -->
-<configuration description="Config for CTS Cronet test cases">
-    <option name="test-suite-tag" value="cts" />
-    <option name="config-descriptor:metadata" key="component" value="networking" />
-    <!-- Instant apps cannot create sockets. See b/264248246 -->
-    <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
-    <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
-    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
-    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
-        <option name="cleanup-apks" value="true" />
-        <option name="test-file-name" value="CtsNetHttpTestCases.apk" />
-    </target_preparer>
-    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
-        <option name="package" value="android.net.http.cts" />
-        <option name="runtime-hint" value="10s" />
-    </test>
-
-    <!-- Only run CtsNetHttpTestCases in MTS if the Tethering Mainline module is installed. -->
-    <object type="module_controller"
-            class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
-        <option name="mainline-module-package-name" value="com.google.android.tethering" />
-    </object>
-</configuration>
diff --git a/Cronet/tests/cts/assets/html/hello_world.html b/Cronet/tests/cts/assets/html/hello_world.html
deleted file mode 100644
index ea62ce2..0000000
--- a/Cronet/tests/cts/assets/html/hello_world.html
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-  ~ Copyright (C) 2022 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.
-  -->
-
-<html>
-<head>
-    <title>hello world</title>
-</head>
-<body>
-<h3>hello world</h3><br>
-</body>
-</html>
\ No newline at end of file
diff --git a/Cronet/tests/cts/res/xml/network_security_config.xml b/Cronet/tests/cts/res/xml/network_security_config.xml
deleted file mode 100644
index 7d7530b..0000000
--- a/Cronet/tests/cts/res/xml/network_security_config.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<!--
-  ~ Copyright (C) 2022 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-security-config>
-    <domain-config cleartextTrafficPermitted="true">
-        <domain includeSubdomains="true">localhost</domain>
-    </domain-config>
-</network-security-config>
\ No newline at end of file
diff --git a/Cronet/tests/cts/src/android/net/http/cts/BidirectionalStreamTest.kt b/Cronet/tests/cts/src/android/net/http/cts/BidirectionalStreamTest.kt
deleted file mode 100644
index 464862d..0000000
--- a/Cronet/tests/cts/src/android/net/http/cts/BidirectionalStreamTest.kt
+++ /dev/null
@@ -1,201 +0,0 @@
-/*
- * 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 android.os.Build
-import androidx.test.core.app.ApplicationProvider
-import com.android.testutils.DevSdkIgnoreRule
-import com.android.testutils.DevSdkIgnoreRunner
-import com.android.testutils.SkipPresubmit
-import com.google.common.truth.Truth.assertThat
-import kotlin.test.Test
-import kotlin.test.assertEquals
-import org.hamcrest.MatcherAssert
-import org.hamcrest.Matchers
-import org.junit.After
-import org.junit.AssumptionViolatedException
-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(DevSdkIgnoreRunner::class)
-@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
-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() {
-        httpEngine.shutdown()
-    }
-
-    private fun createBidirectionalStreamBuilder(url: String): BidirectionalStream.Builder {
-        return httpEngine.newBidirectionalStreamBuilder(url, callback.executor, callback)
-    }
-
-    @Test
-    @Throws(Exception::class)
-    @SkipPresubmit(reason = "b/293141085 Confirm non-flaky and move to presubmit after SLO")
-    fun testBidirectionalStream_GetStream_CompletesSuccessfully() {
-        stream = createBidirectionalStreamBuilder(URL).setHttpMethod("GET").build()
-        stream!!.start()
-        // We call to a real server and hence the server may not be reachable, cancel this stream
-        // and rethrow the exception before tearDown,
-        // otherwise shutdown would fail with active request error.
-        try {
-            callback.assumeCallback(ResponseStep.ON_SUCCEEDED)
-        } catch (e: AssumptionViolatedException) {
-            stream!!.cancel()
-            callback.blockForDone()
-            throw e
-        }
-
-        val info = callback.mResponseInfo
-        assumeOKStatusCode(info)
-        MatcherAssert.assertThat(
-            "Received byte count must be > 0", info.receivedByteCount, Matchers.greaterThan(0L))
-        assertEquals("h2", info.negotiatedProtocol)
-    }
-
-    @Test
-    @Throws(Exception::class)
-    fun testBidirectionalStream_getHttpMethod() {
-        val builder = createBidirectionalStreamBuilder(URL)
-        val method = "GET"
-
-        builder.setHttpMethod(method)
-        stream = builder.build()
-        assertThat(stream!!.getHttpMethod()).isEqualTo(method)
-    }
-
-    @Test
-    @Throws(Exception::class)
-    fun testBidirectionalStream_hasTrafficStatsTag() {
-        val builder = createBidirectionalStreamBuilder(URL)
-
-        builder.setTrafficStatsTag(10)
-        stream = builder.build()
-        assertThat(stream!!.hasTrafficStatsTag()).isTrue()
-    }
-
-    @Test
-    @Throws(Exception::class)
-    fun testBidirectionalStream_getTrafficStatsTag() {
-        val builder = createBidirectionalStreamBuilder(URL)
-        val trafficStatsTag = 10
-
-        builder.setTrafficStatsTag(trafficStatsTag)
-        stream = builder.build()
-        assertThat(stream!!.getTrafficStatsTag()).isEqualTo(trafficStatsTag)
-    }
-
-    @Test
-    @Throws(Exception::class)
-    fun testBidirectionalStream_hasTrafficStatsUid() {
-        val builder = createBidirectionalStreamBuilder(URL)
-
-        builder.setTrafficStatsUid(10)
-        stream = builder.build()
-        assertThat(stream!!.hasTrafficStatsUid()).isTrue()
-    }
-
-    @Test
-    @Throws(Exception::class)
-    fun testBidirectionalStream_getTrafficStatsUid() {
-        val builder = createBidirectionalStreamBuilder(URL)
-        val trafficStatsUid = 10
-
-        builder.setTrafficStatsUid(trafficStatsUid)
-        stream = builder.build()
-        assertThat(stream!!.getTrafficStatsUid()).isEqualTo(trafficStatsUid)
-    }
-
-    @Test
-    @Throws(Exception::class)
-    fun testBidirectionalStream_getHeaders_asList() {
-        val builder = createBidirectionalStreamBuilder(URL)
-        val expectedHeaders = mapOf(
-          "Authorization" to "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==",
-          "Max-Forwards" to "10",
-          "X-Client-Data" to "random custom header content").entries.toList()
-
-        for (header in expectedHeaders) {
-            builder.addHeader(header.key, header.value)
-        }
-
-        stream = builder.build()
-        assertThat(stream!!.getHeaders().getAsList()).containsAtLeastElementsIn(expectedHeaders)
-    }
-
-    @Test
-    @Throws(Exception::class)
-    fun testBidirectionalStream_getHeaders_asMap() {
-        val builder = createBidirectionalStreamBuilder(URL)
-        val expectedHeaders = mapOf(
-          "Authorization" to listOf("Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=="),
-          "Max-Forwards" to listOf("10"),
-          "X-Client-Data" to listOf("random custom header content"))
-
-        for (header in expectedHeaders) {
-            builder.addHeader(header.key, header.value.get(0))
-        }
-
-        stream = builder.build()
-        assertThat(stream!!.getHeaders().getAsMap()).containsAtLeastEntriesIn(expectedHeaders)
-    }
-
-    @Test
-    @Throws(Exception::class)
-    fun testBidirectionalStream_getPriority() {
-        val builder = createBidirectionalStreamBuilder(URL)
-        val priority = BidirectionalStream.STREAM_PRIORITY_LOW
-
-        builder.setPriority(priority)
-        stream = builder.build()
-        assertThat(stream!!.getPriority()).isEqualTo(priority)
-    }
-
-    @Test
-    @Throws(Exception::class)
-    fun testBidirectionalStream_isDelayRequestHeadersUntilFirstFlushEnabled() {
-        val builder = createBidirectionalStreamBuilder(URL)
-
-        builder.setDelayRequestHeadersUntilFirstFlushEnabled(true)
-        stream = builder.build()
-        assertThat(stream!!.isDelayRequestHeadersUntilFirstFlushEnabled()).isTrue()
-    }
-}
diff --git a/Cronet/tests/cts/src/android/net/http/cts/CallbackExceptionTest.kt b/Cronet/tests/cts/src/android/net/http/cts/CallbackExceptionTest.kt
deleted file mode 100644
index 1405ed9..0000000
--- a/Cronet/tests/cts/src/android/net/http/cts/CallbackExceptionTest.kt
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * 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.CallbackException
-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.FailureType
-import android.net.http.cts.util.TestUrlRequestCallback.ResponseStep
-import android.os.Build
-import androidx.test.core.app.ApplicationProvider
-import com.android.testutils.DevSdkIgnoreRule
-import com.android.testutils.DevSdkIgnoreRunner
-import kotlin.test.Test
-import kotlin.test.assertEquals
-import kotlin.test.assertIs
-import kotlin.test.assertSame
-import kotlin.test.assertTrue
-import org.junit.runner.RunWith
-
-@RunWith(DevSdkIgnoreRunner::class)
-@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
-class CallbackExceptionTest {
-
-    @Test
-    fun testCallbackException_returnsInputParameters() {
-        val message = "failed"
-        val cause = Throwable("exception")
-        val callbackException = object : CallbackException(message, cause) {}
-
-        assertEquals(message, callbackException.message)
-        assertSame(cause, callbackException.cause)
-    }
-
-    @Test
-    fun testCallbackException_thrownFromUrlRequest() {
-        val context: Context = ApplicationProvider.getApplicationContext()
-        val server = HttpCtsTestServer(context)
-        val httpEngine = HttpEngine.Builder(context).build()
-        val callback = TestUrlRequestCallback()
-        callback.setFailure(FailureType.THROW_SYNC, ResponseStep.ON_RESPONSE_STARTED)
-        val request = httpEngine
-            .newUrlRequestBuilder(server.successUrl, callback.executor, callback)
-            .build()
-
-        request.start()
-        callback.blockForDone()
-
-        assertTrue(request.isDone)
-        assertIs<CallbackException>(callback.mError)
-        server.shutdown()
-        httpEngine.shutdown()
-    }
-}
diff --git a/Cronet/tests/cts/src/android/net/http/cts/ConnectionMigrationOptionsTest.kt b/Cronet/tests/cts/src/android/net/http/cts/ConnectionMigrationOptionsTest.kt
deleted file mode 100644
index 10c7f3c..0000000
--- a/Cronet/tests/cts/src/android/net/http/cts/ConnectionMigrationOptionsTest.kt
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * 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.ConnectionMigrationOptions
-import android.net.http.ConnectionMigrationOptions.MIGRATION_OPTION_ENABLED
-import android.net.http.ConnectionMigrationOptions.MIGRATION_OPTION_UNSPECIFIED
-import android.os.Build
-import com.android.testutils.DevSdkIgnoreRule
-import com.android.testutils.DevSdkIgnoreRunner
-import kotlin.test.Test
-import kotlin.test.assertEquals
-import org.junit.runner.RunWith
-
-@RunWith(DevSdkIgnoreRunner::class)
-@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
-class ConnectionMigrationOptionsTest {
-
-    @Test
-    fun testConnectionMigrationOptions_defaultValues() {
-        val options =
-                ConnectionMigrationOptions.Builder().build()
-
-        assertEquals(MIGRATION_OPTION_UNSPECIFIED, options.allowNonDefaultNetworkUsage)
-        assertEquals(MIGRATION_OPTION_UNSPECIFIED, options.defaultNetworkMigration)
-        assertEquals(MIGRATION_OPTION_UNSPECIFIED, options.pathDegradationMigration)
-    }
-
-    @Test
-    fun testConnectionMigrationOptions_enableDefaultNetworkMigration_returnSetValue() {
-        val options =
-            ConnectionMigrationOptions.Builder()
-                    .setDefaultNetworkMigration(MIGRATION_OPTION_ENABLED)
-                    .build()
-
-        assertEquals(MIGRATION_OPTION_ENABLED, options.defaultNetworkMigration)
-    }
-
-    @Test
-    fun testConnectionMigrationOptions_enablePathDegradationMigration_returnSetValue() {
-        val options =
-            ConnectionMigrationOptions.Builder()
-                    .setPathDegradationMigration(MIGRATION_OPTION_ENABLED)
-                    .build()
-
-        assertEquals(MIGRATION_OPTION_ENABLED, options.pathDegradationMigration)
-    }
-
-    @Test
-    fun testConnectionMigrationOptions_allowNonDefaultNetworkUsage_returnSetValue() {
-        val options =
-                ConnectionMigrationOptions.Builder()
-                        .setAllowNonDefaultNetworkUsage(MIGRATION_OPTION_ENABLED).build()
-
-        assertEquals(MIGRATION_OPTION_ENABLED, options.allowNonDefaultNetworkUsage)
-    }
-}
diff --git a/Cronet/tests/cts/src/android/net/http/cts/DnsOptionsTest.kt b/Cronet/tests/cts/src/android/net/http/cts/DnsOptionsTest.kt
deleted file mode 100644
index 56802c6..0000000
--- a/Cronet/tests/cts/src/android/net/http/cts/DnsOptionsTest.kt
+++ /dev/null
@@ -1,153 +0,0 @@
-/*
- * 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.DnsOptions
-import android.net.http.DnsOptions.DNS_OPTION_ENABLED
-import android.net.http.DnsOptions.DNS_OPTION_UNSPECIFIED
-import android.os.Build
-import com.android.testutils.DevSdkIgnoreRule
-import com.android.testutils.DevSdkIgnoreRunner
-import java.time.Duration
-import kotlin.test.Test
-import kotlin.test.assertEquals
-import kotlin.test.assertNotNull
-import kotlin.test.assertNull
-import org.junit.runner.RunWith
-
-@RunWith(DevSdkIgnoreRunner::class)
-@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
-class DnsOptionsTest {
-
-    @Test
-    fun testDnsOptions_defaultValues() {
-        val options = DnsOptions.Builder().build()
-
-        assertEquals(DNS_OPTION_UNSPECIFIED, options.persistHostCache)
-        assertNull(options.persistHostCachePeriod)
-        assertEquals(DNS_OPTION_UNSPECIFIED, options.staleDns)
-        assertNull(options.staleDnsOptions)
-        assertEquals(DNS_OPTION_UNSPECIFIED, options.useHttpStackDnsResolver)
-        assertEquals(DNS_OPTION_UNSPECIFIED,
-                options.preestablishConnectionsToStaleDnsResults)
-    }
-
-    @Test
-    fun testDnsOptions_persistHostCache_returnSetValue() {
-        val options = DnsOptions.Builder()
-                .setPersistHostCache(DNS_OPTION_ENABLED)
-                .build()
-
-        assertEquals(DNS_OPTION_ENABLED, options.persistHostCache)
-    }
-
-    @Test
-    fun testDnsOptions_persistHostCachePeriod_returnSetValue() {
-        val period = Duration.ofMillis(12345)
-        val options = DnsOptions.Builder().setPersistHostCachePeriod(period).build()
-
-        assertEquals(period, options.persistHostCachePeriod)
-    }
-
-    @Test
-    fun testDnsOptions_enableStaleDns_returnSetValue() {
-        val options = DnsOptions.Builder()
-                .setStaleDns(DNS_OPTION_ENABLED)
-                .build()
-
-        assertEquals(DNS_OPTION_ENABLED, options.staleDns)
-    }
-
-    @Test
-    fun testDnsOptions_useHttpStackDnsResolver_returnsSetValue() {
-        val options = DnsOptions.Builder()
-                .setUseHttpStackDnsResolver(DNS_OPTION_ENABLED)
-                .build()
-
-        assertEquals(DNS_OPTION_ENABLED, options.useHttpStackDnsResolver)
-    }
-
-    @Test
-    fun testDnsOptions_preestablishConnectionsToStaleDnsResults_returnsSetValue() {
-        val options = DnsOptions.Builder()
-                .setPreestablishConnectionsToStaleDnsResults(DNS_OPTION_ENABLED)
-                .build()
-
-        assertEquals(DNS_OPTION_ENABLED,
-                options.preestablishConnectionsToStaleDnsResults)
-    }
-
-    @Test
-    fun testDnsOptions_setStaleDnsOptions_returnsSetValues() {
-        val staleOptions = DnsOptions.StaleDnsOptions.Builder()
-                .setAllowCrossNetworkUsage(DNS_OPTION_ENABLED)
-                .setFreshLookupTimeout(Duration.ofMillis(1234))
-                .build()
-        val options = DnsOptions.Builder()
-                .setStaleDns(DNS_OPTION_ENABLED)
-                .setStaleDnsOptions(staleOptions)
-                .build()
-
-        assertEquals(DNS_OPTION_ENABLED, options.staleDns)
-        assertEquals(staleOptions, options.staleDnsOptions)
-    }
-
-    @Test
-    fun testStaleDnsOptions_defaultValues() {
-        val options = DnsOptions.StaleDnsOptions.Builder().build()
-
-        assertEquals(DNS_OPTION_UNSPECIFIED, options.allowCrossNetworkUsage)
-        assertNull(options.freshLookupTimeout)
-        assertNull(options.maxExpiredDelay)
-        assertEquals(DNS_OPTION_UNSPECIFIED, options.useStaleOnNameNotResolved)
-    }
-
-    @Test
-    fun testStaleDnsOptions_allowCrossNetworkUsage_returnsSetValue() {
-        val options = DnsOptions.StaleDnsOptions.Builder()
-                .setAllowCrossNetworkUsage(DNS_OPTION_ENABLED).build()
-
-        assertEquals(DNS_OPTION_ENABLED, options.allowCrossNetworkUsage)
-    }
-
-    @Test
-    fun testStaleDnsOptions_freshLookupTimeout_returnsSetValue() {
-        val duration = Duration.ofMillis(12345)
-        val options = DnsOptions.StaleDnsOptions.Builder().setFreshLookupTimeout(duration).build()
-
-        assertNotNull(options.freshLookupTimeout)
-        assertEquals(duration, options.freshLookupTimeout!!)
-    }
-
-    @Test
-    fun testStaleDnsOptions_useStaleOnNameNotResolved_returnsSetValue() {
-        val options = DnsOptions.StaleDnsOptions.Builder()
-                .setUseStaleOnNameNotResolved(DNS_OPTION_ENABLED)
-                .build()
-
-        assertEquals(DNS_OPTION_ENABLED, options.useStaleOnNameNotResolved)
-    }
-
-    @Test
-    fun testStaleDnsOptions_maxExpiredDelayMillis_returnsSetValue() {
-        val duration = Duration.ofMillis(12345)
-        val options = DnsOptions.StaleDnsOptions.Builder().setMaxExpiredDelay(duration).build()
-
-        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
deleted file mode 100644
index f86ac29..0000000
--- a/Cronet/tests/cts/src/android/net/http/cts/HttpEngineTest.java
+++ /dev/null
@@ -1,402 +0,0 @@
-/*
- * 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 static android.net.http.cts.util.TestUtilsKt.assertOKStatusCode;
-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.assertNotNull;
-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.QuicOptions;
-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 android.os.Build;
-
-import androidx.test.core.app.ApplicationProvider;
-
-import com.android.testutils.DevSdkIgnoreRule;
-import com.android.testutils.DevSdkIgnoreRunner;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mockito;
-
-import java.time.Instant;
-import java.util.Arrays;
-import java.util.Calendar;
-import java.util.Set;
-
-@RunWith(DevSdkIgnoreRunner.class)
-@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
-public class HttpEngineTest {
-    private static final String HOST = "source.android.com";
-    private static final String URL = "https://" + HOST;
-
-    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 {
-        mContext = ApplicationProvider.getApplicationContext();
-        skipIfNoInternetConnection(mContext);
-        mEngineBuilder = new HttpEngine.Builder(mContext);
-        mCallback = new TestUrlRequestCallback();
-        mTestServer = new HttpCtsTestServer(mContext);
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        if (mRequest != null) {
-            mRequest.cancel();
-            mCallback.blockForDone();
-        }
-        if (mEngine != null) {
-            mEngine.shutdown();
-        }
-        if (mTestServer != null) {
-            mTestServer.shutdown();
-        }
-    }
-
-    private boolean isQuic(String negotiatedProtocol) {
-        return negotiatedProtocol.startsWith("http/2+quic") || negotiatedProtocol.startsWith("h3");
-    }
-
-    @Test
-    public void testHttpEngine_Default() throws Exception {
-        mEngine = mEngineBuilder.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;
-        assertOKStatusCode(info);
-        assertEquals("h2", info.getNegotiatedProtocol());
-    }
-
-    @Test
-    public void testHttpEngine_EnableHttpCache() {
-        String url = mTestServer.getCacheableTestDownloadUrl(
-                /* downloadId */ "cacheable-download",
-                /* numBytes */ 10);
-        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();
-        mCallback.expectCallback(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.expectCallback(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.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;
-        assertOKStatusCode(info);
-        assertEquals("http/1.1", info.getNegotiatedProtocol());
-    }
-
-    @Test
-    public void testHttpEngine_EnablePublicKeyPinningBypassForLocalTrustAnchors() {
-        String url = mTestServer.getSuccessUrl();
-        // For known hosts, requests should succeed whether we're bypassing the local trust anchor
-        // or not.
-        mEngine = mEngineBuilder.setEnablePublicKeyPinningBypassForLocalTrustAnchors(false).build();
-        UrlRequest.Builder builder =
-                mEngine.newUrlRequestBuilder(url, mCallback.getExecutor(), mCallback);
-        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.
-    }
-
-    private byte[] generateSha256() {
-        byte[] sha256 = new byte[32];
-        Arrays.fill(sha256, (byte) 58);
-        return sha256;
-    }
-
-    private Instant instantInFuture(int secondsIntoFuture) {
-        Calendar cal = Calendar.getInstance();
-        cal.add(Calendar.SECOND, secondsIntoFuture);
-        return cal.getTime().toInstant();
-    }
-
-    @Test
-    public void testHttpEngine_AddPublicKeyPins() {
-        // CtsTestServer, when set in SslMode.NO_CLIENT_AUTH (required to trigger
-        // certificate verification, needed by this test), uses a certificate that
-        // doesn't match the hostname. For this reason, CtsTestServer cannot be used
-        // by this test.
-        Instant expirationInstant = instantInFuture(/* secondsIntoFuture */ 100);
-        boolean includeSubdomains = true;
-        Set<byte[]> pinsSha256 = Set.of(generateSha256());
-        mEngine = mEngineBuilder.addPublicKeyPins(
-                HOST, pinsSha256, includeSubdomains, expirationInstant).build();
-
-        UrlRequest.Builder builder =
-                mEngine.newUrlRequestBuilder(URL, mCallback.getExecutor(), mCallback);
-        mRequest = builder.build();
-        mRequest.start();
-        mCallback.expectCallback(ResponseStep.ON_FAILED);
-        assertNotNull("Expected an error", mCallback.mError);
-    }
-
-    @Test
-    public void testHttpEngine_EnableQuic() throws Exception {
-        String url = mTestServer.getSuccessUrl();
-        mEngine = mEngineBuilder.setEnableQuic(true).addQuicHint(HOST, 443, 443).build();
-        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_GetDefaultUserAgent() throws Exception {
-        assertThat(mEngineBuilder.getDefaultUserAgent(), containsString("AndroidHttpClient"));
-        assertThat(mEngineBuilder.getDefaultUserAgent()).contains(HttpEngine.getVersionString());
-    }
-
-    @Test
-    public void testHttpEngine_requestUsesDefaultUserAgent() throws Exception {
-        mEngine = mEngineBuilder.build();
-
-        String url = mTestServer.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";
-        mEngine =
-                new HttpEngine.Builder(ApplicationProvider.getApplicationContext())
-                        .setUserAgent(userAgent)
-                        .build();
-
-        String url = mTestServer.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);
-    }
-
-    @Test
-    public void getVersionString_notEmpty() {
-        assertThat(HttpEngine.getVersionString()).isNotEmpty();
-    }
-
-    @Test
-    public void testHttpEngine_SetQuicOptions_RequestSucceedsWithQuic() throws Exception {
-        String url = mTestServer.getSuccessUrl();
-        QuicOptions options = new QuicOptions.Builder().build();
-        mEngine = mEngineBuilder
-                .setEnableQuic(true)
-                .addQuicHint(HOST, 443, 443)
-                .setQuicOptions(options)
-                .build();
-        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_enableBrotli_brotliAdvertised() {
-        mEngine = mEngineBuilder.setEnableBrotli(true).build();
-        mRequest =
-                mEngine.newUrlRequestBuilder(
-                        mTestServer.getEchoHeadersUrl(), mCallback.getExecutor(), mCallback)
-                        .build();
-        mRequest.start();
-
-        mCallback.assumeCallback(ResponseStep.ON_SUCCEEDED);
-        UrlResponseInfo info = mCallback.mResponseInfo;
-        assertThat(info.getHeaders().getAsMap().get("x-request-header-Accept-Encoding").toString())
-                .contains("br");
-        assertOKStatusCode(info);
-    }
-}
diff --git a/Cronet/tests/cts/src/android/net/http/cts/NetworkExceptionTest.kt b/Cronet/tests/cts/src/android/net/http/cts/NetworkExceptionTest.kt
deleted file mode 100644
index cff54b3..0000000
--- a/Cronet/tests/cts/src/android/net/http/cts/NetworkExceptionTest.kt
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * 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.HttpEngine
-import android.net.http.NetworkException
-import android.net.http.cts.util.TestUrlRequestCallback
-import android.os.Build
-import androidx.test.core.app.ApplicationProvider
-import com.android.testutils.DevSdkIgnoreRule
-import com.android.testutils.DevSdkIgnoreRunner
-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(DevSdkIgnoreRunner::class)
-@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
-class NetworkExceptionTest {
-
-    @Test
-    fun testNetworkException_returnsInputParameters() {
-        val message = "failed"
-        val cause = Throwable("thrown")
-        val networkException =
-            object : NetworkException(message, cause) {
-                override fun getErrorCode() = 0
-                override fun isImmediatelyRetryable() = false
-            }
-
-        assertEquals(message, networkException.message)
-        assertSame(cause, networkException.cause)
-    }
-
-    @Test
-    fun testNetworkException_thrownFromUrlRequest() {
-        val httpEngine = HttpEngine.Builder(ApplicationProvider.getApplicationContext()).build()
-        val callback = TestUrlRequestCallback()
-        val request =
-            httpEngine.newUrlRequestBuilder("http://localhost", callback.executor, callback).build()
-
-        request.start()
-        callback.blockForDone()
-
-        assertTrue(request.isDone)
-        assertIs<NetworkException>(callback.mError)
-        httpEngine.shutdown()
-    }
-}
diff --git a/Cronet/tests/cts/src/android/net/http/cts/QuicExceptionTest.kt b/Cronet/tests/cts/src/android/net/http/cts/QuicExceptionTest.kt
deleted file mode 100644
index 2705fc3..0000000
--- a/Cronet/tests/cts/src/android/net/http/cts/QuicExceptionTest.kt
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.http.cts
-
-import android.net.http.QuicException
-import android.os.Build
-import com.android.testutils.DevSdkIgnoreRule
-import com.android.testutils.DevSdkIgnoreRunner
-import kotlin.test.Test
-import kotlin.test.assertEquals
-import org.junit.runner.RunWith
-
-@RunWith(DevSdkIgnoreRunner::class)
-@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
-class QuicExceptionTest {
-
-    @Test
-    fun testQuicException_returnsInputParameters() {
-        val message = "failed"
-        val cause = Throwable("thrown")
-        val quicException =
-            object : QuicException(message, cause) {
-                override fun getErrorCode() = 0
-                override fun isImmediatelyRetryable() = false
-            }
-
-        assertEquals(message, quicException.message)
-        assertEquals(cause, quicException.cause)
-    }
-
-    // TODO: add test for QuicException triggered from HttpEngine
-}
diff --git a/Cronet/tests/cts/src/android/net/http/cts/QuicOptionsTest.kt b/Cronet/tests/cts/src/android/net/http/cts/QuicOptionsTest.kt
deleted file mode 100644
index da0b15c..0000000
--- a/Cronet/tests/cts/src/android/net/http/cts/QuicOptionsTest.kt
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * 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 android.os.Build
-import com.android.testutils.DevSdkIgnoreRule
-import com.android.testutils.DevSdkIgnoreRunner
-import com.google.common.truth.Truth.assertThat
-import java.time.Duration
-import kotlin.test.assertFailsWith
-import kotlin.test.assertFalse
-import kotlin.test.assertTrue
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(DevSdkIgnoreRunner::class)
-@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
-class QuicOptionsTest {
-    @Test
-    fun testQuicOptions_defaultValues() {
-        val quicOptions = QuicOptions.Builder().build()
-        assertThat(quicOptions.allowedQuicHosts).isEmpty()
-        assertThat(quicOptions.handshakeUserAgent).isNull()
-        assertThat(quicOptions.idleConnectionTimeout).isNull()
-        assertFalse(quicOptions.hasInMemoryServerConfigsCacheSize())
-        assertFailsWith(IllegalStateException::class) {
-            quicOptions.inMemoryServerConfigsCacheSize
-        }
-    }
-
-    @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()
-        assertTrue(quicOptions.hasInMemoryServerConfigsCacheSize())
-        assertThat(quicOptions.inMemoryServerConfigsCacheSize)
-                .isEqualTo(42)
-    }
-
-    @Test
-    fun testQuicOptions_handshakeUserAgent_returnsSetValue() {
-        val userAgent = "test"
-        val quicOptions = QuicOptions.Builder()
-            .setHandshakeUserAgent(userAgent)
-            .build()
-        assertThat(quicOptions.handshakeUserAgent)
-            .isEqualTo(userAgent)
-    }
-}
diff --git a/Cronet/tests/cts/src/android/net/http/cts/UrlRequestTest.java b/Cronet/tests/cts/src/android/net/http/cts/UrlRequestTest.java
deleted file mode 100644
index 3c4d134..0000000
--- a/Cronet/tests/cts/src/android/net/http/cts/UrlRequestTest.java
+++ /dev/null
@@ -1,526 +0,0 @@
-/*
- * Copyright (C) 2019 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 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.HeaderBlock;
-import android.net.http.HttpEngine;
-import android.net.http.HttpException;
-import android.net.http.InlineExecutionProhibitedException;
-import android.net.http.UploadDataProvider;
-import android.net.http.UrlRequest;
-import android.net.http.UrlRequest.Status;
-import android.net.http.UrlResponseInfo;
-import android.net.http.cts.util.HttpCtsTestServer;
-import android.net.http.cts.util.TestStatusListener;
-import android.net.http.cts.util.TestUrlRequestCallback;
-import android.net.http.cts.util.TestUrlRequestCallback.ResponseStep;
-import android.net.http.cts.util.UploadDataProviders;
-import android.os.Build;
-import android.webkit.cts.CtsTestServer;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.test.core.app.ApplicationProvider;
-
-import com.android.testutils.DevSdkIgnoreRule;
-import com.android.testutils.DevSdkIgnoreRunner;
-
-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.Arrays;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.ArrayBlockingQueue;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.Executor;
-import java.util.concurrent.Executors;
-import java.util.concurrent.TimeUnit;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-
-@RunWith(DevSdkIgnoreRunner.class)
-@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
-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 = ApplicationProvider.getApplicationContext();
-        skipIfNoInternetConnection(context);
-        HttpEngine.Builder builder = new HttpEngine.Builder(context);
-        mHttpEngine = builder.build();
-        mCallback = new TestUrlRequestCallback();
-        mTestServer = new HttpCtsTestServer(context);
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        if (mHttpEngine != null) {
-            mHttpEngine.shutdown();
-        }
-        if (mTestServer != null) {
-            mTestServer.shutdown();
-        }
-    }
-
-    private UrlRequest.Builder createUrlRequestBuilder(String url) {
-        return mHttpEngine.newUrlRequestBuilder(url, mCallback.getExecutor(), mCallback);
-    }
-
-    @Test
-    public void testUrlRequestGet_CompletesSuccessfully() throws Exception {
-        String url = mTestServer.getSuccessUrl();
-        UrlRequest request = createUrlRequestBuilder(url).build();
-        request.start();
-
-        mCallback.expectCallback(ResponseStep.ON_SUCCEEDED);
-        UrlResponseInfo info = mCallback.mResponseInfo;
-        assertOKStatusCode(info);
-        assertThat("Received byte count must be > 0", info.getReceivedByteCount(), greaterThan(0L));
-    }
-
-    @Test
-    public void testUrlRequestStatus_InvalidBeforeRequestStarts() throws Exception {
-        UrlRequest request = createUrlRequestBuilder(mTestServer.getSuccessUrl()).build();
-        // Calling before request is started should give Status.INVALID,
-        // since the native adapter is not created.
-        TestStatusListener statusListener = new TestStatusListener();
-        request.getStatus(statusListener);
-        statusListener.expectStatus(Status.INVALID);
-    }
-
-    @Test
-    public void testUrlRequestCancel_CancelCalled() throws Exception {
-        UrlRequest request = createUrlRequestBuilder(mTestServer.getSuccessUrl()).build();
-        mCallback.setAutoAdvance(false);
-
-        request.start();
-        mCallback.waitForNextStep();
-        assertSame(mCallback.mResponseStep, ResponseStep.ON_RESPONSE_STARTED);
-
-        request.cancel();
-        mCallback.expectCallback(ResponseStep.ON_CANCELED);
-    }
-
-    @Test
-    public void testUrlRequestPost_EchoRequestBody() {
-        String testData = "test";
-        UrlRequest.Builder builder = createUrlRequestBuilder(mTestServer.getEchoBodyUrl());
-
-        UploadDataProvider dataProvider = UploadDataProviders.create(testData);
-        builder.setUploadDataProvider(dataProvider, mCallback.getExecutor());
-        builder.addHeader("Content-Type", "text/html");
-        builder.build().start();
-        mCallback.expectCallback(ResponseStep.ON_SUCCEEDED);
-
-        assertOKStatusCode(mCallback.mResponseInfo);
-        assertEquals(testData, mCallback.mResponseAsString);
-    }
-
-    @Test
-    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 = UploadDataProviders.create("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 = UploadDataProviders.create("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 = UploadDataProviders.create("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);
-    }
-
-    @Test
-    public void testUrlRequest_customHeaders() throws Exception {
-        UrlRequest.Builder builder = createUrlRequestBuilder(mTestServer.getEchoHeadersUrl());
-
-        List<Map.Entry<String, String>> expectedHeaders = Arrays.asList(
-                Map.entry("Authorization", "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=="),
-                Map.entry("Max-Forwards", "10"),
-                Map.entry("X-Client-Data", "random custom header content"));
-
-        for (Map.Entry<String, String> header : expectedHeaders) {
-            builder.addHeader(header.getKey(), header.getValue());
-        }
-
-        builder.build().start();
-        mCallback.expectCallback(ResponseStep.ON_SUCCEEDED);
-
-        assertOKStatusCode(mCallback.mResponseInfo);
-
-        List<Map.Entry<String, String>> echoedHeaders =
-                extractEchoedHeaders(mCallback.mResponseInfo.getHeaders());
-
-        // The implementation might decide to add more headers like accepted encodings it handles
-        // internally so the server is likely to see more headers than explicitly set
-        // by the developer.
-        assertThat(echoedHeaders)
-                .containsAtLeastElementsIn(expectedHeaders);
-    }
-
-    @Test
-    public void testUrlRequest_getHttpMethod() throws Exception {
-        UrlRequest.Builder builder = createUrlRequestBuilder(mTestServer.getSuccessUrl());
-        final String method = "POST";
-
-        builder.setHttpMethod(method);
-        UrlRequest request = builder.build();
-        assertThat(request.getHttpMethod()).isEqualTo(method);
-    }
-
-    @Test
-    public void testUrlRequest_getHeaders_asList() throws Exception {
-        UrlRequest.Builder builder = createUrlRequestBuilder(mTestServer.getSuccessUrl());
-        final List<Map.Entry<String, String>> expectedHeaders = Arrays.asList(
-                Map.entry("Authorization", "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=="),
-                Map.entry("Max-Forwards", "10"),
-                Map.entry("X-Client-Data", "random custom header content"));
-
-        for (Map.Entry<String, String> header : expectedHeaders) {
-            builder.addHeader(header.getKey(), header.getValue());
-        }
-
-        UrlRequest request = builder.build();
-        assertThat(request.getHeaders().getAsList()).containsAtLeastElementsIn(expectedHeaders);
-    }
-
-    @Test
-    public void testUrlRequest_getHeaders_asMap() throws Exception {
-        UrlRequest.Builder builder = createUrlRequestBuilder(mTestServer.getSuccessUrl());
-        final Map<String, List<String>> expectedHeaders = Map.of(
-                "Authorization", Arrays.asList("Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=="),
-                "Max-Forwards", Arrays.asList("10"),
-                "X-Client-Data", Arrays.asList("random custom header content"));
-
-        for (Map.Entry<String, List<String>> header : expectedHeaders.entrySet()) {
-            builder.addHeader(header.getKey(), header.getValue().get(0));
-        }
-
-        UrlRequest request = builder.build();
-        assertThat(request.getHeaders().getAsMap()).containsAtLeastEntriesIn(expectedHeaders);
-    }
-
-    @Test
-    public void testUrlRequest_isCacheDisabled() throws Exception {
-        UrlRequest.Builder builder = createUrlRequestBuilder(mTestServer.getSuccessUrl());
-        final boolean isCacheDisabled = true;
-
-        builder.setCacheDisabled(isCacheDisabled);
-        UrlRequest request = builder.build();
-        assertThat(request.isCacheDisabled()).isEqualTo(isCacheDisabled);
-    }
-
-    @Test
-    public void testUrlRequest_isDirectExecutorAllowed() throws Exception {
-        UrlRequest.Builder builder = createUrlRequestBuilder(mTestServer.getSuccessUrl());
-        final boolean isDirectExecutorAllowed = true;
-
-        builder.setDirectExecutorAllowed(isDirectExecutorAllowed);
-        UrlRequest request = builder.build();
-        assertThat(request.isDirectExecutorAllowed()).isEqualTo(isDirectExecutorAllowed);
-    }
-
-    @Test
-    public void testUrlRequest_getPriority() throws Exception {
-        UrlRequest.Builder builder = createUrlRequestBuilder(mTestServer.getSuccessUrl());
-        final int priority = UrlRequest.REQUEST_PRIORITY_LOW;
-
-        builder.setPriority(priority);
-        UrlRequest request = builder.build();
-        assertThat(request.getPriority()).isEqualTo(priority);
-    }
-
-    @Test
-    public void testUrlRequest_hasTrafficStatsTag() throws Exception {
-        UrlRequest.Builder builder = createUrlRequestBuilder(mTestServer.getSuccessUrl());
-
-        builder.setTrafficStatsTag(10);
-        UrlRequest request = builder.build();
-        assertThat(request.hasTrafficStatsTag()).isEqualTo(true);
-    }
-
-    @Test
-    public void testUrlRequest_getTrafficStatsTag() throws Exception {
-        UrlRequest.Builder builder = createUrlRequestBuilder(mTestServer.getSuccessUrl());
-        final int trafficStatsTag = 10;
-
-        builder.setTrafficStatsTag(trafficStatsTag);
-        UrlRequest request = builder.build();
-        assertThat(request.getTrafficStatsTag()).isEqualTo(trafficStatsTag);
-    }
-
-    @Test
-    public void testUrlRequest_hasTrafficStatsUid() throws Exception {
-        UrlRequest.Builder builder = createUrlRequestBuilder(mTestServer.getSuccessUrl());
-
-        builder.setTrafficStatsUid(10);
-        UrlRequest request = builder.build();
-        assertThat(request.hasTrafficStatsUid()).isEqualTo(true);
-    }
-
-    @Test
-    public void testUrlRequest_getTrafficStatsUid() throws Exception {
-        UrlRequest.Builder builder = createUrlRequestBuilder(mTestServer.getSuccessUrl());
-        final int trafficStatsUid = 10;
-
-        builder.setTrafficStatsUid(trafficStatsUid);
-        UrlRequest request = builder.build();
-        assertThat(request.getTrafficStatsUid()).isEqualTo(trafficStatsUid);
-    }
-
-    private static List<Map.Entry<String, String>> extractEchoedHeaders(HeaderBlock headers) {
-        return headers.getAsList()
-                .stream()
-                .flatMap(input -> {
-                    if (input.getKey().startsWith(CtsTestServer.ECHOED_RESPONSE_HEADER_PREFIX)) {
-                        String strippedKey =
-                                input.getKey().substring(
-                                        CtsTestServer.ECHOED_RESPONSE_HEADER_PREFIX.length());
-                        return Stream.of(Map.entry(strippedKey, input.getValue()));
-                    } else {
-                        return Stream.empty();
-                    }
-                })
-                .collect(Collectors.toList());
-    }
-
-    private static class StubUrlRequestCallback implements UrlRequest.Callback {
-
-        @Override
-        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();
-        }
-    }
-}
diff --git a/Cronet/tests/cts/src/android/net/http/cts/UrlResponseInfoTest.kt b/Cronet/tests/cts/src/android/net/http/cts/UrlResponseInfoTest.kt
deleted file mode 100644
index f1b57c6..0000000
--- a/Cronet/tests/cts/src/android/net/http/cts/UrlResponseInfoTest.kt
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * 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 android.os.Build
-import androidx.test.core.app.ApplicationProvider
-import com.android.testutils.DevSdkIgnoreRule
-import com.android.testutils.DevSdkIgnoreRunner
-import kotlin.test.Test
-import kotlin.test.assertEquals
-import kotlin.test.assertFalse
-import kotlin.test.assertTrue
-import org.junit.runner.RunWith
-
-@RunWith(DevSdkIgnoreRunner::class)
-@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
-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/HttpCtsTestServer.kt b/Cronet/tests/cts/src/android/net/http/cts/util/HttpCtsTestServer.kt
deleted file mode 100644
index 5196544..0000000
--- a/Cronet/tests/cts/src/android/net/http/cts/util/HttpCtsTestServer.kt
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2022 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.content.Context
-import android.webkit.cts.CtsTestServer
-import java.net.URI
-import org.apache.http.HttpEntityEnclosingRequest
-import org.apache.http.HttpRequest
-import org.apache.http.HttpResponse
-import org.apache.http.HttpStatus
-import org.apache.http.HttpVersion
-import org.apache.http.message.BasicHttpResponse
-
-private const val ECHO_BODY_PATH = "/echo_body"
-
-/** Extends CtsTestServer to handle POST requests and other test specific requests */
-class HttpCtsTestServer(context: Context) : CtsTestServer(context) {
-
-    val echoBodyUrl: String = baseUri + ECHO_BODY_PATH
-    val successUrl: String = getAssetUrl("html/hello_world.html")
-
-    override fun onPost(req: HttpRequest): HttpResponse? {
-        val path = URI.create(req.requestLine.uri).path
-        var response: HttpResponse? = null
-
-        if (path.startsWith(ECHO_BODY_PATH)) {
-            if (req !is HttpEntityEnclosingRequest) {
-                return BasicHttpResponse(
-                    HttpVersion.HTTP_1_0,
-                    HttpStatus.SC_INTERNAL_SERVER_ERROR,
-                    "Expected req to be of type HttpEntityEnclosingRequest but got ${req.javaClass}"
-                )
-            }
-
-            response = BasicHttpResponse(HttpVersion.HTTP_1_0, HttpStatus.SC_OK, null)
-            response.entity = req.entity
-            response.addHeader("Content-Length", req.entity.contentLength.toString())
-        }
-
-        return response
-    }
-}
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
deleted file mode 100644
index 1e7333c..0000000
--- a/Cronet/tests/cts/src/android/net/http/cts/util/TestBidirectionalStreamCallback.java
+++ /dev/null
@@ -1,485 +0,0 @@
-/*
- * 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
deleted file mode 100644
index 3a4486f..0000000
--- a/Cronet/tests/cts/src/android/net/http/cts/util/TestStatusListener.kt
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2022 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.UrlRequest.StatusListener
-import java.util.concurrent.CompletableFuture
-import java.util.concurrent.TimeUnit
-import org.junit.Assert.assertSame
-
-private const val TIMEOUT_MS = 12000L
-
-/** Test status listener for requests */
-class TestStatusListener : StatusListener {
-    private val statusFuture = CompletableFuture<Int>()
-
-    override fun onStatus(status: Int) {
-        statusFuture.complete(status)
-    }
-
-    /** Fails if the expected status is not the returned status */
-    fun expectStatus(expected: Int) {
-        assertSame(expected, statusFuture.get(TIMEOUT_MS, TimeUnit.MILLISECONDS))
-    }
-}
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
deleted file mode 100644
index 28443b7..0000000
--- a/Cronet/tests/cts/src/android/net/http/cts/util/TestUrlRequestCallback.java
+++ /dev/null
@@ -1,481 +0,0 @@
-/*
- * Copyright (C) 2022 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.hamcrest.core.AnyOf.anyOf;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertThat;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-import static org.junit.Assume.assumeThat;
-import static org.junit.Assume.assumeTrue;
-
-import android.net.http.CallbackException;
-import android.net.http.HttpException;
-import android.net.http.InlineExecutionProhibitedException;
-import android.net.http.UrlRequest;
-import android.net.http.UrlResponseInfo;
-import android.os.ConditionVariable;
-import android.os.StrictMode;
-
-import java.nio.ByteBuffer;
-import java.util.ArrayList;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ThreadFactory;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Callback that tracks information from different callbacks and has a
- * 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 implements UrlRequest.Callback {
-    private static final int TIMEOUT_MS = 12_000;
-    public ArrayList<UrlResponseInfo> mRedirectResponseInfoList = new ArrayList<>();
-    public ArrayList<String> mRedirectUrlList = new ArrayList<>();
-    public UrlResponseInfo mResponseInfo;
-    public HttpException mError;
-
-    public ResponseStep mResponseStep = ResponseStep.NOTHING;
-
-    public int mRedirectCount;
-    public boolean mOnErrorCalled;
-    public boolean mOnCanceledCalled;
-
-    public int mHttpResponseDataLength;
-    public String mResponseAsString = "";
-
-    public int mReadBufferSize = 32 * 1024;
-
-    // When false, the consumer is responsible for all calls into the request
-    // that advance it.
-    private boolean mAutoAdvance = true;
-    // Whether an exception is thrown by maybeThrowCancelOrPause().
-    private boolean mCallbackExceptionThrown;
-
-    // Whether to permit calls on the network thread.
-    private boolean mAllowDirectExecutor;
-
-    // Whether to stop the executor thread after reaching a terminal method.
-    // Terminal methods are (onSucceeded, onFailed or onCancelled)
-    private boolean mBlockOnTerminalState;
-
-    // Conditionally fail on certain steps.
-    private FailureType mFailureType = FailureType.NONE;
-    private ResponseStep mFailureStep = ResponseStep.NOTHING;
-
-    // Signals when request is done either successfully or not.
-    private final ConditionVariable mDone = new ConditionVariable();
-
-    // Hangs the calling thread until a terminal method has started executing.
-    private final ConditionVariable mWaitForTerminalToStart = new ConditionVariable();
-
-    // Signaled on each step when mAutoAdvance is false.
-    private final ConditionVariable mStepBlock = new ConditionVariable();
-
-    // Executor Service for Http callbacks.
-    private final ExecutorService mExecutorService;
-    private Thread mExecutorThread;
-
-    // position() of ByteBuffer prior to read() call.
-    private int mBufferPositionBeforeRead;
-
-    private static class ExecutorThreadFactory implements ThreadFactory {
-        @Override
-        public Thread newThread(final Runnable r) {
-            return new Thread(new Runnable() {
-                @Override
-                public void run() {
-                    StrictMode.ThreadPolicy threadPolicy = StrictMode.getThreadPolicy();
-                    try {
-                        StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
-                                .detectNetwork()
-                                .penaltyLog()
-                                .penaltyDeath()
-                                .build());
-                        r.run();
-                    } finally {
-                        StrictMode.setThreadPolicy(threadPolicy);
-                    }
-                }
-            });
-        }
-    }
-
-    public enum ResponseStep {
-        NOTHING,
-        ON_RECEIVED_REDIRECT,
-        ON_RESPONSE_STARTED,
-        ON_READ_COMPLETED,
-        ON_SUCCEEDED,
-        ON_FAILED,
-        ON_CANCELED,
-    }
-
-    public enum FailureType {
-        NONE,
-        CANCEL_SYNC,
-        CANCEL_ASYNC,
-        // Same as above, but continues to advance the request after posting
-        // the cancellation task.
-        CANCEL_ASYNC_WITHOUT_PAUSE,
-        THROW_SYNC
-    }
-
-    private static void assertContains(String expectedSubstring, String actualString) {
-        assertNotNull(actualString);
-        assertTrue("String [" + actualString + "] doesn't contain substring [" + expectedSubstring
-                + "]", actualString.contains(expectedSubstring));
-
-    }
-
-    /**
-     * Set {@code mExecutorThread}.
-     */
-    private void fillInExecutorThread() {
-        mExecutorService.execute(new Runnable() {
-            @Override
-            public void run() {
-                mExecutorThread = Thread.currentThread();
-            }
-        });
-    }
-
-    private boolean isTerminalCallback(ResponseStep step) {
-        switch (step) {
-            case ON_SUCCEEDED:
-            case ON_CANCELED:
-            case ON_FAILED:
-                return true;
-            default:
-                return false;
-        }
-    }
-
-    /**
-     * Create a {@link TestUrlRequestCallback} with a new single-threaded executor.
-     */
-    public TestUrlRequestCallback() {
-        this(Executors.newSingleThreadExecutor(new ExecutorThreadFactory()));
-    }
-
-    /**
-     * Create a {@link TestUrlRequestCallback} using a custom single-threaded executor.
-     */
-    public TestUrlRequestCallback(ExecutorService executorService) {
-        mExecutorService = executorService;
-        fillInExecutorThread();
-    }
-
-    /**
-     * This blocks the callback executor thread once it has reached a final state callback.
-     * In order to continue execution, this method must be called again and providing {@code false}
-     * to continue execution.
-     *
-     * @param blockOnTerminalState the state to set for the executor thread
-     */
-    public void setBlockOnTerminalState(boolean blockOnTerminalState) {
-        mBlockOnTerminalState = blockOnTerminalState;
-        if (!blockOnTerminalState) {
-            mDone.open();
-        }
-    }
-
-    public void setAutoAdvance(boolean autoAdvance) {
-        mAutoAdvance = autoAdvance;
-    }
-
-    public void setAllowDirectExecutor(boolean allowed) {
-        mAllowDirectExecutor = allowed;
-    }
-
-    public void setFailure(FailureType failureType, ResponseStep failureStep) {
-        mFailureStep = failureStep;
-        mFailureType = failureType;
-    }
-
-    /**
-     * Blocks the calling thread till callback execution is done
-     *
-     * @return true if the condition was opened, false if the call returns because of the timeout.
-     */
-    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("Did not receive terminal callback before timeout", 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("Did not receive terminal callback before timeout", blockForDone());
-        }
-        assumeThat(expectedStep, equalTo(mResponseStep));
-    }
-
-    /**
-     * Blocks the calling thread until one of the final states has been called.
-     * This is called before the callback has finished executed.
-     */
-    public void waitForTerminalToStart() {
-        mWaitForTerminalToStart.block();
-    }
-
-    public void waitForNextStep() {
-        mStepBlock.block();
-        mStepBlock.close();
-    }
-
-    public ExecutorService getExecutor() {
-        return mExecutorService;
-    }
-
-    public void shutdownExecutor() {
-        mExecutorService.shutdown();
-    }
-
-    /**
-     * Shuts down the ExecutorService and waits until it executes all posted
-     * tasks.
-     */
-    public void shutdownExecutorAndWait() {
-        mExecutorService.shutdown();
-        try {
-            // Termination shouldn't take long. Use 1 min which should be more than enough.
-            mExecutorService.awaitTermination(1, TimeUnit.MINUTES);
-        } catch (InterruptedException e) {
-            fail("ExecutorService is interrupted while waiting for termination");
-        }
-        assertTrue(mExecutorService.isTerminated());
-    }
-
-    @Override
-    public void onRedirectReceived(
-            UrlRequest request, UrlResponseInfo info, String newLocationUrl) {
-        checkExecutorThread();
-        assertFalse(request.isDone());
-        assertThat(mResponseStep, anyOf(
-                equalTo(ResponseStep.NOTHING),
-                equalTo(ResponseStep.ON_RECEIVED_REDIRECT)));
-        assertNull(mError);
-
-        mResponseStep = ResponseStep.ON_RECEIVED_REDIRECT;
-        mRedirectUrlList.add(newLocationUrl);
-        mRedirectResponseInfoList.add(info);
-        ++mRedirectCount;
-        if (maybeThrowCancelOrPause(request)) {
-            return;
-        }
-        request.followRedirect();
-    }
-
-    @Override
-    public void onResponseStarted(UrlRequest request, UrlResponseInfo info) {
-        checkExecutorThread();
-        assertFalse(request.isDone());
-        assertThat(mResponseStep, anyOf(
-                equalTo(ResponseStep.NOTHING),
-                equalTo(ResponseStep.ON_RECEIVED_REDIRECT)));
-        assertNull(mError);
-
-        mResponseStep = ResponseStep.ON_RESPONSE_STARTED;
-        mResponseInfo = info;
-        if (maybeThrowCancelOrPause(request)) {
-            return;
-        }
-        startNextRead(request);
-    }
-
-    @Override
-    public void onReadCompleted(UrlRequest request, UrlResponseInfo info, ByteBuffer byteBuffer) {
-        checkExecutorThread();
-        assertFalse(request.isDone());
-        assertThat(mResponseStep, anyOf(
-                equalTo(ResponseStep.ON_RESPONSE_STARTED),
-                equalTo(ResponseStep.ON_READ_COMPLETED)));
-        assertNull(mError);
-
-        mResponseStep = ResponseStep.ON_READ_COMPLETED;
-
-        final byte[] lastDataReceivedAsBytes;
-        final int bytesRead = byteBuffer.position() - mBufferPositionBeforeRead;
-        mHttpResponseDataLength += bytesRead;
-        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(request)) {
-            return;
-        }
-        startNextRead(request);
-    }
-
-    @Override
-    public void onSucceeded(UrlRequest request, UrlResponseInfo info) {
-        checkExecutorThread();
-        assertTrue(request.isDone());
-        assertThat(mResponseStep, anyOf(
-                equalTo(ResponseStep.ON_RESPONSE_STARTED),
-                equalTo(ResponseStep.ON_READ_COMPLETED)));
-        assertFalse(mOnErrorCalled);
-        assertFalse(mOnCanceledCalled);
-        assertNull(mError);
-
-        mResponseStep = ResponseStep.ON_SUCCEEDED;
-        mResponseInfo = info;
-        mWaitForTerminalToStart.open();
-        if (mBlockOnTerminalState) mDone.block();
-        openDone();
-        maybeThrowCancelOrPause(request);
-    }
-
-    @Override
-    public void onFailed(UrlRequest request, UrlResponseInfo info, HttpException error) {
-        // If the failure is because of prohibited direct execution, the test shouldn't fail
-        // since the request already did.
-        if (error.getCause() instanceof InlineExecutionProhibitedException) {
-            mAllowDirectExecutor = true;
-        }
-        checkExecutorThread();
-        assertTrue(request.isDone());
-        // Shouldn't happen after success.
-        assertNotEquals(ResponseStep.ON_SUCCEEDED, mResponseStep);
-        // Should happen at most once for a single request.
-        assertFalse(mOnErrorCalled);
-        assertFalse(mOnCanceledCalled);
-        assertNull(mError);
-        if (mCallbackExceptionThrown) {
-            assertTrue(error instanceof CallbackException);
-            assertContains("Exception received from UrlRequest.Callback", error.getMessage());
-            assertNotNull(error.getCause());
-            assertTrue(error.getCause() instanceof IllegalStateException);
-            assertContains("Listener Exception.", error.getCause().getMessage());
-        }
-
-        mResponseStep = ResponseStep.ON_FAILED;
-        mOnErrorCalled = true;
-        mError = error;
-        mWaitForTerminalToStart.open();
-        if (mBlockOnTerminalState) mDone.block();
-        openDone();
-        maybeThrowCancelOrPause(request);
-    }
-
-    @Override
-    public void onCanceled(UrlRequest request, UrlResponseInfo info) {
-        checkExecutorThread();
-        assertTrue(request.isDone());
-        // Should happen at most once for a single request.
-        assertFalse(mOnCanceledCalled);
-        assertFalse(mOnErrorCalled);
-        assertNull(mError);
-
-        mResponseStep = ResponseStep.ON_CANCELED;
-        mOnCanceledCalled = true;
-        mWaitForTerminalToStart.open();
-        if (mBlockOnTerminalState) mDone.block();
-        openDone();
-        maybeThrowCancelOrPause(request);
-    }
-
-    public void startNextRead(UrlRequest request) {
-        startNextRead(request, ByteBuffer.allocateDirect(mReadBufferSize));
-    }
-
-    public void startNextRead(UrlRequest request, ByteBuffer buffer) {
-        mBufferPositionBeforeRead = buffer.position();
-        request.read(buffer);
-    }
-
-    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);
-    }
-
-    protected void openDone() {
-        mDone.open();
-    }
-
-    private void checkExecutorThread() {
-        if (!mAllowDirectExecutor) {
-            assertEquals(mExecutorThread, Thread.currentThread());
-        }
-    }
-
-    /**
-     * Returns {@code false} if the listener should continue to advance the
-     * request.
-     */
-    private boolean maybeThrowCancelOrPause(final UrlRequest request) {
-        checkExecutorThread();
-        if (mResponseStep != mFailureStep || mFailureType == FailureType.NONE) {
-            if (!mAutoAdvance) {
-                mStepBlock.open();
-                return true;
-            }
-            return false;
-        }
-
-        if (mFailureType == FailureType.THROW_SYNC) {
-            assertFalse(mCallbackExceptionThrown);
-            mCallbackExceptionThrown = true;
-            throw new IllegalStateException("Listener Exception.");
-        }
-        Runnable task = new Runnable() {
-            @Override
-            public void run() {
-                request.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;
-    }
-}
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
deleted file mode 100644
index 7fc005a..0000000
--- a/Cronet/tests/cts/src/android/net/http/cts/util/TestUtils.kt
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * 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.content.Context
-import android.net.ConnectivityManager
-import android.net.http.UrlResponseInfo
-import org.hamcrest.Matchers.equalTo
-import org.junit.Assert.assertEquals
-import org.junit.Assume.assumeNotNull
-import org.junit.Assume.assumeThat
-
-fun skipIfNoInternetConnection(context: Context) {
-    val connectivityManager = context.getSystemService(ConnectivityManager::class.java)
-    assumeNotNull(
-        "This test requires a working Internet connection", connectivityManager!!.activeNetwork
-    )
-}
-
-fun assertOKStatusCode(info: UrlResponseInfo) {
-    assertEquals("Status code must be 200 OK", 200, info.httpStatusCode)
-}
-
-fun assumeOKStatusCode(info: UrlResponseInfo) {
-    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
deleted file mode 100644
index 3b90fa0..0000000
--- a/Cronet/tests/cts/src/android/net/http/cts/util/UploadDataProviders.java
+++ /dev/null
@@ -1,209 +0,0 @@
-/*
- * 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;
-import java.nio.charset.StandardCharsets;
-
-/**
- * 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);
-    }
-
-    /**
-     * Uploads the UTF-8 representation of {@code data}
-     *
-     * @param data String containing data to upload
-     * @return A new UploadDataProvider for the given data
-     */
-    public static UploadDataProvider create(String data) {
-        return create(data.getBytes(StandardCharsets.UTF_8));
-    }
-
-    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
deleted file mode 100644
index 9486e1f..0000000
--- a/Cronet/tests/mts/Android.bp
+++ /dev/null
@@ -1,68 +0,0 @@
-// 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_team: "trendy_team_fwk_core_networking",
-    // See: http://go/android-license-faq
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-java_genrule {
-    name: "net-http-test-jarjar-rules",
-    tool_files: [
-        ":NetHttpTestsLibPreJarJar{.jar}",
-        "jarjar_excludes.txt",
-    ],
-    tools: [
-        "jarjar-rules-generator",
-    ],
-    out: ["net_http_test_jarjar_rules.txt"],
-    cmd: "$(location jarjar-rules-generator) " +
-        "$(location :NetHttpTestsLibPreJarJar{.jar}) " +
-        "--prefix android.net.connectivity " +
-        "--excludes $(location jarjar_excludes.txt) " +
-        "--output $(out)",
-}
-
-// Library to be used in coverage tests. cronet_java_tests can't be used directly because the common
-// tests need to inherit the NetHttpTests manifest.
-android_library {
-    name: "NetHttpTestsLibPreJarJar",
-    static_libs: [
-        "cronet_aml_api_java",
-        "cronet_aml_java__testing",
-        "cronet_java_tests",
-    ],
-    sdk_version: "module_current",
-    min_sdk_version: "30",
-}
-
-android_test {
-    name: "NetHttpTests",
-    defaults: [
-        "mts-target-sdk-version-current",
-    ],
-    static_libs: ["NetHttpTestsLibPreJarJar"],
-    jarjar_rules: ":net-http-test-jarjar-rules",
-    jni_libs: [
-        "cronet_aml_components_cronet_android_cronet__testing",
-        "cronet_aml_components_cronet_android_cronet_tests__testing",
-        "cronet_aml_third_party_netty_tcnative_netty_tcnative_so__testing",
-    ],
-    test_suites: [
-        "general-tests",
-        "mts-tethering",
-    ],
-    data: [":cronet_javatests_resources"],
-}
diff --git a/Cronet/tests/mts/AndroidManifest.xml b/Cronet/tests/mts/AndroidManifest.xml
deleted file mode 100644
index 2c56e3a..0000000
--- a/Cronet/tests/mts/AndroidManifest.xml
+++ /dev/null
@@ -1,33 +0,0 @@
-<?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="android.net.http.mts">
-
-    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
-    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
-    <uses-permission android:name="android.permission.INTERNET"/>
-
-    <application android:networkSecurityConfig="@xml/network_security_config">
-        <uses-library android:name="android.test.runner" />
-    </application>
-    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="android.net.http.mts"
-                     android:label="MTS tests of android.net.http">
-    </instrumentation>
-
-</manifest>
\ No newline at end of file
diff --git a/Cronet/tests/mts/AndroidTest.xml b/Cronet/tests/mts/AndroidTest.xml
deleted file mode 100644
index a438e2e..0000000
--- a/Cronet/tests/mts/AndroidTest.xml
+++ /dev/null
@@ -1,58 +0,0 @@
-<?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.
-  -->
-<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.Sdk31ModuleController" />
-
-    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
-        <option name="test-file-name" value="NetHttpTests.apk" />
-    </target_preparer>
-
-    <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
-        <option name="push-file" key="net" value="/storage/emulated/0/chromium_tests_root/net" />
-        <option name="push-file" key="test_server" value="/storage/emulated/0/chromium_tests_root/components/cronet/testing/test_server" />
-    </target_preparer>
-
-    <option name="test-tag" value="NetHttpTests" />
-    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
-        <option name="package" value="android.net.http.mts" />
-        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
-        <!-- b/298380508 -->
-        <option name="exclude-filter" value="org.chromium.net.CronetUrlRequestContextTest#testSetLibraryLoaderIsEnforcedByDefaultEmbeddedProvider" />
-        <!-- b/316559294 -->
-        <option name="exclude-filter" value="org.chromium.net.NQETest#testQuicDisabled" />
-        <!-- b/316559294 -->
-        <option name="exclude-filter" value="org.chromium.net.NQETest#testPrefsWriteRead" />
-        <!-- b/316554711-->
-        <option name="exclude-filter" value="org.chromium.net.NetworkChangesTest" />
-        <!-- b/316550794 -->
-        <option name="exclude-filter" value="org.chromium.net.impl.CronetLoggerTest#testEngineCreation" />
-        <!-- b/327182569 -->
-        <option name="exclude-filter" value="org.chromium.net.urlconnection.CronetURLStreamHandlerFactoryTest#testSetUrlStreamFactoryUsesCronetForNative" />
-        <option name="hidden-api-checks" value="false"/>
-        <option name="isolated-storage" value="false"/>
-        <option name="orchestrator" value="true"/>
-    </test>
-
-    <!-- Only run NetHttpTests in MTS if the Tethering Mainline module is installed. -->
-    <object type="module_controller"
-            class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
-        <option name="mainline-module-package-name" value="com.google.android.tethering" />
-    </object>
-</configuration>
diff --git a/Cronet/tests/mts/jarjar_excludes.txt b/Cronet/tests/mts/jarjar_excludes.txt
deleted file mode 100644
index b5cdf6e..0000000
--- a/Cronet/tests/mts/jarjar_excludes.txt
+++ /dev/null
@@ -1,29 +0,0 @@
-# Exclude some test prefixes, as they can't be found after being jarjared.
-com\.android\.testutils\..+
-# jarjar-gen can't handle some kotlin object expression, exclude packages that include them
-androidx\..+
-# don't jarjar netty as it does JNI
-io\.netty\..+
-kotlin\.test\..+
-kotlin\.reflect\..+
-org\.mockito\..+
-# Do not jarjar the api classes
-android\.net\..+
-# cronet_tests.so is not jarjared and uses base classes. We can remove this when there's a
-# separate java base target to depend on.
-org\.chromium\.base\..+
-J\.cronet_tests_N(\$.+)?
-
-# don't jarjar automatically generated FooJni files.
-org\.chromium\.net\..+Jni(\$.+)?
-
-# Do not jarjar the tests and its utils as they also do JNI with cronet_tests.so
-org\.chromium\.net\..*Test.*(\$.+)?
-org\.chromium\.net\.NativeTestServer(\$.+)?
-org\.chromium\.net\.MockUrlRequestJobFactory(\$.+)?
-org\.chromium\.net\.QuicTestServer(\$.+)?
-org\.chromium\.net\.MockCertVerifier(\$.+)?
-org\.chromium\.net\.LogcatCapture(\$.+)?
-org\.chromium\.net\.ReportingCollector(\$.+)?
-org\.chromium\.net\.Http2TestServer(\$.+)?
-org\.chromium\.net\.Http2TestHandler(\$.+)?
\ No newline at end of file
diff --git a/Cronet/tests/mts/res/raw/quicroot.pem b/Cronet/tests/mts/res/raw/quicroot.pem
deleted file mode 100644
index af21b3e..0000000
--- a/Cronet/tests/mts/res/raw/quicroot.pem
+++ /dev/null
@@ -1,19 +0,0 @@
------BEGIN CERTIFICATE-----
-MIIC/jCCAeagAwIBAgIUXOi6XoxnMUjJg4jeOwRhsdqEqEQwDQYJKoZIhvcNAQEL
-BQAwFzEVMBMGA1UEAwwMVGVzdCBSb290IENBMB4XDTIzMDYwMTExMjcwMFoXDTMz
-MDUyOTExMjcwMFowFzEVMBMGA1UEAwwMVGVzdCBSb290IENBMIIBIjANBgkqhkiG
-9w0BAQEFAAOCAQ8AMIIBCgKCAQEAl9xCMPMIvfmJWz25AG/VtgWbqNs67HXQbXWf
-pDF2wjQpHVOYbfl7Zgly5O+5es1aUbJaGyZ9G6xuYSXKFnnYLoP7M86O05fQQBAj
-K+IE5nO6136ksCAfxCFTFfn4vhPvK8Vba5rqox4WeIXYKvHYSoiHz0ELrnFOHcyN
-Innyze7bLtkMCA1ShHpmvDCR+U3Uj6JwOfoirn29jjU/48/ORha7dcJYtYXk2eGo
-RJfrtIx20tXAaKaGnXOCGYbEVXTeQkQPqKFVzqP7+KYS/Y8eNFV35ugpLNES+44T
-bQ2QruTZdrNRjJkEoyiB/E53a0OUltB/R7Z0L0xstnKfsAf3OwIDAQABo0IwQDAP
-BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUVdXNh2lk
-51/6hMmz0Z+OpIe8+f0wDQYJKoZIhvcNAQELBQADggEBADNg7G8n6DUrQ5doXzm9
-kOp5siX6iPs0zFReXKhIT1Gef63l3tb7AdPedF03aj9XkUt0shhNOGG5SK2k5KBQ
-MJc9muYRCAyo2xMr3rFUQdI5B51SCy5HeAMralgTHXN0Hv+TH04YfRrACVmr+5ke
-pH3bF1gYaT+Zy5/pHJnV5lcwS6/H44g9XXWIopjWCwbfzKxIuWofqL4fiToPSIYu
-MCUI4bKZipcJT5O6rdz/S9lbgYVjOJ4HAoT2icNQqNMMfULKevmF8SdJzfNd35yn
-tAKTROhIE2aQRVCclrjo/T3eyjWGGoJlGmxKbeCf/rXzcn1BRtk/UzLnbUFFlg5l
-axw=
------END CERTIFICATE-----
\ No newline at end of file
diff --git a/Cronet/tests/mts/res/values/cronet-test-rule-configuration.xml b/Cronet/tests/mts/res/values/cronet-test-rule-configuration.xml
deleted file mode 100644
index 48ce420..0000000
--- a/Cronet/tests/mts/res/values/cronet-test-rule-configuration.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?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.
-  -->
-
-<resources>
-    <bool name="is_running_in_aosp">true</bool>
-</resources>
\ No newline at end of file
diff --git a/Cronet/tests/mts/res/xml/network_security_config.xml b/Cronet/tests/mts/res/xml/network_security_config.xml
deleted file mode 100644
index 32b7171..0000000
--- a/Cronet/tests/mts/res/xml/network_security_config.xml
+++ /dev/null
@@ -1,47 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<!--
-  ~ Copyright (C) 2022 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-security-config>
-  <base-config>
-    <trust-anchors>
-      <certificates src="@raw/quicroot"/>
-      <certificates src="system"/>
-    </trust-anchors>
-  </base-config>
-  <!-- Since Android 9 (API 28) cleartext support is disabled by default, this
-       causes some of our tests to fail (see crbug/1220357).
-       The following configs allow http requests for the domains used in these
-       tests.
-
-       TODO(stefanoduo): Figure out if we really need to use http for these tests
-  -->
-  <domain-config cleartextTrafficPermitted="true">
-    <!-- Used as the base URL by native test server (net::EmbeddedTestServer) -->
-    <domain includeSubdomains="true">127.0.0.1</domain>
-    <!-- Used by CronetHttpURLConnectionTest#testIOExceptionInterruptRethrown -->
-    <domain includeSubdomains="true">localhost</domain>
-    <!-- Used by CronetHttpURLConnectionTest#testBadIP -->
-    <domain includeSubdomains="true">0.0.0.0</domain>
-    <!-- Used by CronetHttpURLConnectionTest#testSetUseCachesFalse -->
-    <domain includeSubdomains="true">host-cache-test-host</domain>
-    <!-- Used by CronetHttpURLConnectionTest#testBadHostname -->
-    <domain includeSubdomains="true">this-weird-host-name-does-not-exist</domain>
-    <!-- Used by CronetUrlRequestContextTest#testHostResolverRules -->
-    <domain includeSubdomains="true">some-weird-hostname</domain>
-  </domain-config>
-</network-security-config>
\ No newline at end of file
diff --git a/TEST_MAPPING b/TEST_MAPPING
index d8d4c21..375397b 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -1,4 +1,152 @@
 {
+  "captiveportal-networkstack-resolve-tethering-mainline-presubmit": [
+    {
+      "name": "CtsNetTestCasesLatestSdk",
+      "options": [
+        {
+          "exclude-annotation": "com.android.testutils.SkipPresubmit"
+        },
+        {
+          "exclude-annotation": "com.android.testutils.SkipMainlinePresubmit"
+        },
+        {
+          "exclude-annotation": "androidx.test.filters.RequiresDevice"
+        }
+      ]
+    },
+    {
+      "name": "CtsNetTestCasesMaxTargetSdk30",
+      "options": [
+        {
+          "exclude-annotation": "com.android.testutils.SkipPresubmit"
+        },
+        {
+          "exclude-annotation": "com.android.testutils.SkipMainlinePresubmit"
+        },
+        {
+          "exclude-annotation": "androidx.test.filters.RequiresDevice"
+        }
+      ]
+    },
+    {
+      "name": "CtsNetTestCasesMaxTargetSdk31",
+      "options": [
+        {
+          "exclude-annotation": "com.android.testutils.SkipPresubmit"
+        },
+        {
+          "exclude-annotation": "com.android.testutils.SkipMainlinePresubmit"
+        },
+        {
+          "exclude-annotation": "androidx.test.filters.RequiresDevice"
+        }
+      ]
+    },
+    {
+      "name": "CtsNetTestCasesMaxTargetSdk33",
+      "options": [
+        {
+          "exclude-annotation": "com.android.testutils.SkipPresubmit"
+        },
+        {
+          "exclude-annotation": "com.android.testutils.SkipMainlinePresubmit"
+        },
+        {
+          "exclude-annotation": "androidx.test.filters.RequiresDevice"
+        }
+      ]
+    },
+    {
+      "name": "bpf_existence_test"
+    },
+    {
+      "name": "connectivity_native_test"
+    },
+    {
+      "name": "netd_updatable_unit_test"
+    },
+    {
+      "name": "ConnectivityCoverageTests",
+      "options": [
+        {
+          "exclude-annotation": "com.android.testutils.SkipPresubmit"
+        }
+      ]
+    },
+    {
+      "name": "libnetworkstats_test"
+    },
+    {
+      "name": "NetHttpCoverageTests",
+      "options": [
+        {
+          "exclude-annotation": "com.android.testutils.SkipPresubmit"
+        },
+        {
+          // These sometimes take longer than 1 min which is the presubmit timeout
+          "exclude-annotation": "androidx.test.filters.LargeTest"
+        }
+      ]
+    },
+    {
+      "name": "CtsTetheringTestLatestSdk",
+      "options": [
+        {
+          "exclude-annotation": "com.android.testutils.NetworkStackModuleTest"
+        }
+      ]
+    }
+  ],
+  "captiveportal-networkstack-mainline-presubmit": [
+    // Test with APK modules only, in cases where APEX is not supported, or the other modules
+    // were simply not updated
+    {
+      "name": "CtsNetTestCasesLatestSdk",
+      "options": [
+        {
+          "exclude-annotation": "com.android.testutils.SkipPresubmit"
+        },
+        {
+          "exclude-annotation": "com.android.testutils.SkipMainlinePresubmit"
+        },
+        {
+          "exclude-annotation": "androidx.test.filters.RequiresDevice"
+        },
+        {
+          "exclude-annotation": "com.android.testutils.ConnectivityModuleTest"
+        },
+        {
+          "exclude-annotation": "com.android.testutils.DnsResolverModuleTest"
+        }
+      ]
+    }
+  ],
+  "tethering-mainline-presubmit": [
+    // Test with connectivity/tethering module only, to catch integration issues with older versions
+    // of other modules. "new tethering + old NetworkStack" is not a configuration that should
+    // really exist in the field, but there is no strong guarantee, and it is required by MTS
+    // testing for module qualification, where modules are tested independently.
+    {
+      "name": "CtsNetTestCasesLatestSdk",
+      "options": [
+        {
+          "exclude-annotation": "com.android.testutils.SkipPresubmit"
+        },
+        {
+          "exclude-annotation": "com.android.testutils.SkipMainlinePresubmit"
+        },
+        {
+          "exclude-annotation": "androidx.test.filters.RequiresDevice"
+        },
+        {
+          "exclude-annotation": "com.android.testutils.DnsResolverModuleTest"
+        },
+        {
+          "exclude-annotation": "com.android.testutils.NetworkStackModuleTest"
+        }
+      ]
+    }
+  ],
   "presubmit": [
     {
       "name": "ConnectivityCoverageTests",
@@ -104,19 +252,6 @@
     },
     {
       "name": "FrameworksNetIntegrationTests"
-    },
-    // Runs both NetHttpTests and CtsNetHttpTestCases
-    {
-      "name": "NetHttpCoverageTests",
-      "options": [
-        {
-          "exclude-annotation": "com.android.testutils.SkipPresubmit"
-        },
-        {
-          // These sometimes take longer than 1 min which is the presubmit timeout
-          "exclude-annotation": "androidx.test.filters.LargeTest"
-        }
-      ]
     }
   ],
   "postsubmit": [
@@ -144,9 +279,6 @@
     },
     {
       "name": "FrameworksNetTests"
-    },
-    {
-      "name": "NetHttpCoverageTests"
     }
   ],
   "mainline-presubmit": [
@@ -273,18 +405,6 @@
       "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]",
-      "options": [
-        {
-          "exclude-annotation": "com.android.testutils.SkipPresubmit"
-        },
-        {
-          // These sometimes take longer than 1 min which is the presubmit timeout
-          "exclude-annotation": "androidx.test.filters.LargeTest"
-        }
-      ]
-    },
-    {
       "name": "CtsTetheringTestLatestSdk[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]",
       "options": [
         {
@@ -332,6 +452,9 @@
       "path": "packages/modules/CaptivePortalLogin"
     },
     {
+      "path": "external/cronet"
+    },
+    {
       "path": "vendor/xts/gts-tests/hostsidetests/networkstack"
     }
   ]
diff --git a/common/FlaggedApi.bp b/common/FlaggedApi.bp
index 56625c5..21be1d3 100644
--- a/common/FlaggedApi.bp
+++ b/common/FlaggedApi.bp
@@ -17,7 +17,7 @@
 aconfig_declarations {
     name: "com.android.net.flags-aconfig",
     package: "com.android.net.flags",
-    container: "system",
+    container: "com.android.tethering",
     srcs: ["flags.aconfig"],
     visibility: ["//packages/modules/Connectivity:__subpackages__"],
 }
@@ -25,7 +25,7 @@
 aconfig_declarations {
     name: "com.android.net.thread.flags-aconfig",
     package: "com.android.net.thread.flags",
-    container: "system",
+    container: "com.android.tethering",
     srcs: ["thread_flags.aconfig"],
     visibility: ["//packages/modules/Connectivity:__subpackages__"],
 }
@@ -33,7 +33,7 @@
 aconfig_declarations {
     name: "nearby_flags",
     package: "com.android.nearby.flags",
-    container: "system",
+    container: "com.android.tethering",
     srcs: ["nearby_flags.aconfig"],
     visibility: ["//packages/modules/Connectivity:__subpackages__"],
 }
diff --git a/common/flags.aconfig b/common/flags.aconfig
index 512677a..55a96ac 100644
--- a/common/flags.aconfig
+++ b/common/flags.aconfig
@@ -1,5 +1,5 @@
 package: "com.android.net.flags"
-container: "system"
+container: "com.android.tethering"
 
 # This file contains aconfig flags for FlaggedAPI annotations
 # Flags used from platform code must be in under frameworks
@@ -91,3 +91,11 @@
   description: "Flag for metered network firewall chain API"
   bug: "332628891"
 }
+
+flag {
+  name: "blocked_reason_oem_deny_chains"
+  is_exported: true
+  namespace: "android_core_networking"
+  description: "Flag for oem deny chains blocked reasons API"
+  bug: "328732146"
+}
diff --git a/common/nearby_flags.aconfig b/common/nearby_flags.aconfig
index b733849..55a865b 100644
--- a/common/nearby_flags.aconfig
+++ b/common/nearby_flags.aconfig
@@ -1,5 +1,5 @@
 package: "com.android.nearby.flags"
-container: "system"
+container: "com.android.tethering"
 
 flag {
     name: "powered_off_finding"
diff --git a/common/thread_flags.aconfig b/common/thread_flags.aconfig
index 43fc147..43acd1b 100644
--- a/common/thread_flags.aconfig
+++ b/common/thread_flags.aconfig
@@ -1,5 +1,5 @@
 package: "com.android.net.thread.flags"
-container: "system"
+container: "com.android.tethering"
 
 flag {
     name: "thread_enabled"
diff --git a/framework-t/src/android/net/IpSecTransform.java b/framework-t/src/android/net/IpSecTransform.java
index 4e10a96..70c9bc8 100644
--- a/framework-t/src/android/net/IpSecTransform.java
+++ b/framework-t/src/android/net/IpSecTransform.java
@@ -124,7 +124,7 @@
     private IpSecTransform activate()
             throws IOException, IpSecManager.ResourceUnavailableException,
                     IpSecManager.SpiUnavailableException {
-        synchronized (this) {
+        synchronized (mLock) {
             try {
                 IpSecTransformResponse result = getIpSecManager(mContext).createTransform(
                         mConfig, new Binder(), mContext.getOpPackageName());
@@ -164,20 +164,23 @@
     public void close() {
         Log.d(TAG, "Removing Transform with Id " + mResourceId);
 
-        // Always safe to attempt cleanup
-        if (mResourceId == INVALID_RESOURCE_ID) {
-            mCloseGuard.close();
-            return;
-        }
-        try {
-            getIpSecManager(mContext).deleteTransform(mResourceId);
-        } catch (Exception e) {
-            // On close we swallow all random exceptions since failure to close is not
-            // actionable by the user.
-            Log.e(TAG, "Failed to close " + this + ", Exception=" + e);
-        } finally {
-            mResourceId = INVALID_RESOURCE_ID;
-            mCloseGuard.close();
+        synchronized(mLock) {
+            // Always safe to attempt cleanup
+            if (mResourceId == INVALID_RESOURCE_ID) {
+                mCloseGuard.close();
+                return;
+            }
+
+            try {
+                    getIpSecManager(mContext).deleteTransform(mResourceId);
+            } catch (Exception e) {
+                // On close we swallow all random exceptions since failure to close is not
+                // actionable by the user.
+                Log.e(TAG, "Failed to close " + this + ", Exception=" + e);
+            } finally {
+                mResourceId = INVALID_RESOURCE_ID;
+                mCloseGuard.close();
+            }
         }
     }
 
@@ -196,14 +199,17 @@
     }
 
     private final IpSecConfig mConfig;
-    private int mResourceId;
+    private final Object mLock = new Object();
+    private int mResourceId; // Partly guarded by mLock to ensure basic safety, not correctness
     private final Context mContext;
     private final CloseGuard mCloseGuard = CloseGuard.get();
 
     /** @hide */
     @VisibleForTesting
     public int getResourceId() {
-        return mResourceId;
+        synchronized(mLock) {
+            return mResourceId;
+        }
     }
 
     /**
@@ -224,8 +230,10 @@
         // TODO: Consider adding check to prevent DDoS attack.
 
         try {
-            final IpSecTransformState ipSecTransformState =
-                    getIpSecManager(mContext).getTransformState(mResourceId);
+            IpSecTransformState ipSecTransformState;
+            synchronized(mLock) {
+                ipSecTransformState = getIpSecManager(mContext).getTransformState(mResourceId);
+            }
             executor.execute(
                     () -> {
                         callback.onResult(ipSecTransformState);
diff --git a/framework/api/module-lib-current.txt b/framework/api/module-lib-current.txt
index b2aafa0..d233f3e 100644
--- a/framework/api/module-lib-current.txt
+++ b/framework/api/module-lib-current.txt
@@ -52,6 +52,7 @@
     field public static final int BLOCKED_REASON_LOCKDOWN_VPN = 16; // 0x10
     field public static final int BLOCKED_REASON_LOW_POWER_STANDBY = 32; // 0x20
     field public static final int BLOCKED_REASON_NONE = 0; // 0x0
+    field @FlaggedApi("com.android.net.flags.blocked_reason_oem_deny_chains") public static final int BLOCKED_REASON_OEM_DENY = 128; // 0x80
     field public static final int BLOCKED_REASON_RESTRICTED_MODE = 8; // 0x8
     field @FlaggedApi("com.android.net.flags.basic_background_restrictions_enabled") public static final int FIREWALL_CHAIN_BACKGROUND = 6; // 0x6
     field public static final int FIREWALL_CHAIN_DOZABLE = 1; // 0x1
diff --git a/framework/src/android/net/BpfNetMapsUtils.java b/framework/src/android/net/BpfNetMapsUtils.java
index 4e01fee..4099e2a 100644
--- a/framework/src/android/net/BpfNetMapsUtils.java
+++ b/framework/src/android/net/BpfNetMapsUtils.java
@@ -37,6 +37,18 @@
 import static android.net.BpfNetMapsConstants.RESTRICTED_MATCH;
 import static android.net.BpfNetMapsConstants.STANDBY_MATCH;
 import static android.net.BpfNetMapsConstants.UID_RULES_CONFIGURATION_KEY;
+import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_ADMIN_DISABLED;
+import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_DATA_SAVER;
+import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_MASK;
+import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_USER_RESTRICTED;
+import static android.net.ConnectivityManager.BLOCKED_REASON_APP_BACKGROUND;
+import static android.net.ConnectivityManager.BLOCKED_REASON_APP_STANDBY;
+import static android.net.ConnectivityManager.BLOCKED_REASON_BATTERY_SAVER;
+import static android.net.ConnectivityManager.BLOCKED_REASON_DOZE;
+import static android.net.ConnectivityManager.BLOCKED_REASON_LOW_POWER_STANDBY;
+import static android.net.ConnectivityManager.BLOCKED_REASON_NONE;
+import static android.net.ConnectivityManager.BLOCKED_REASON_OEM_DENY;
+import static android.net.ConnectivityManager.BLOCKED_REASON_RESTRICTED_MODE;
 import static android.net.ConnectivityManager.FIREWALL_CHAIN_BACKGROUND;
 import static android.net.ConnectivityManager.FIREWALL_CHAIN_DOZABLE;
 import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY;
@@ -238,6 +250,67 @@
     }
 
     /**
+     * Get blocked reasons for specified uid
+     *
+     * @param uid Target Uid
+     * @return Reasons of network access blocking for an UID
+     */
+    public static int getUidNetworkingBlockedReasons(final int uid,
+            IBpfMap<S32, U32> configurationMap,
+            IBpfMap<S32, UidOwnerValue> uidOwnerMap,
+            IBpfMap<S32, U8> dataSaverEnabledMap
+    ) {
+        final long uidRuleConfig;
+        final long uidMatch;
+        try {
+            uidRuleConfig = configurationMap.getValue(UID_RULES_CONFIGURATION_KEY).val;
+            final UidOwnerValue value = uidOwnerMap.getValue(new Struct.S32(uid));
+            uidMatch = (value != null) ? value.rule : 0L;
+        } catch (ErrnoException e) {
+            throw new ServiceSpecificException(e.errno,
+                    "Unable to get firewall chain status: " + Os.strerror(e.errno));
+        }
+        final long blockingMatches = (uidRuleConfig & ~uidMatch & sMaskDropIfUnset)
+                | (uidRuleConfig & uidMatch & sMaskDropIfSet);
+
+        int blockedReasons = BLOCKED_REASON_NONE;
+        if ((blockingMatches & POWERSAVE_MATCH) != 0) {
+            blockedReasons |= BLOCKED_REASON_BATTERY_SAVER;
+        }
+        if ((blockingMatches & DOZABLE_MATCH) != 0) {
+            blockedReasons |= BLOCKED_REASON_DOZE;
+        }
+        if ((blockingMatches & STANDBY_MATCH) != 0) {
+            blockedReasons |= BLOCKED_REASON_APP_STANDBY;
+        }
+        if ((blockingMatches & RESTRICTED_MATCH) != 0) {
+            blockedReasons |= BLOCKED_REASON_RESTRICTED_MODE;
+        }
+        if ((blockingMatches & LOW_POWER_STANDBY_MATCH) != 0) {
+            blockedReasons |= BLOCKED_REASON_LOW_POWER_STANDBY;
+        }
+        if ((blockingMatches & BACKGROUND_MATCH) != 0) {
+            blockedReasons |= BLOCKED_REASON_APP_BACKGROUND;
+        }
+        if ((blockingMatches & (OEM_DENY_1_MATCH | OEM_DENY_2_MATCH | OEM_DENY_3_MATCH)) != 0) {
+            blockedReasons |= BLOCKED_REASON_OEM_DENY;
+        }
+
+        // Metered chains are not enabled by configuration map currently.
+        if ((uidMatch & PENALTY_BOX_USER_MATCH) != 0) {
+            blockedReasons |= BLOCKED_METERED_REASON_USER_RESTRICTED;
+        }
+        if ((uidMatch & PENALTY_BOX_ADMIN_MATCH) != 0) {
+            blockedReasons |= BLOCKED_METERED_REASON_ADMIN_DISABLED;
+        }
+        if ((uidMatch & HAPPY_BOX_MATCH) == 0 && getDataSaverEnabled(dataSaverEnabledMap)) {
+            blockedReasons |= BLOCKED_METERED_REASON_DATA_SAVER;
+        }
+
+        return blockedReasons;
+    }
+
+    /**
      * Return whether the network is blocked by firewall chains for the given uid.
      *
      * Note that {@link #getDataSaverEnabled(IBpfMap)} has a latency before V.
@@ -263,27 +336,16 @@
             return false;
         }
 
-        final long uidRuleConfig;
-        final long uidMatch;
-        try {
-            uidRuleConfig = configurationMap.getValue(UID_RULES_CONFIGURATION_KEY).val;
-            final UidOwnerValue value = uidOwnerMap.getValue(new Struct.S32(uid));
-            uidMatch = (value != null) ? value.rule : 0L;
-        } catch (ErrnoException e) {
-            throw new ServiceSpecificException(e.errno,
-                    "Unable to get firewall chain status: " + Os.strerror(e.errno));
+        final int blockedReasons = getUidNetworkingBlockedReasons(
+                uid,
+                configurationMap,
+                uidOwnerMap,
+                dataSaverEnabledMap);
+        if (isNetworkMetered) {
+            return blockedReasons != BLOCKED_REASON_NONE;
+        } else {
+            return (blockedReasons & ~BLOCKED_METERED_REASON_MASK) != BLOCKED_REASON_NONE;
         }
-
-        final boolean blockedByAllowChains = 0 != (uidRuleConfig & ~uidMatch & sMaskDropIfUnset);
-        final boolean blockedByDenyChains = 0 != (uidRuleConfig & uidMatch & sMaskDropIfSet);
-        if (blockedByAllowChains || blockedByDenyChains) {
-            return true;
-        }
-
-        if (!isNetworkMetered) return false;
-        if ((uidMatch & (PENALTY_BOX_USER_MATCH | PENALTY_BOX_ADMIN_MATCH)) != 0) return true;
-        if ((uidMatch & HAPPY_BOX_MATCH) != 0) return false;
-        return getDataSaverEnabled(dataSaverEnabledMap);
     }
 
     /**
diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java
index 7823258..48ed732 100644
--- a/framework/src/android/net/ConnectivityManager.java
+++ b/framework/src/android/net/ConnectivityManager.java
@@ -130,6 +130,8 @@
                 "com.android.net.flags.basic_background_restrictions_enabled";
         static final String METERED_NETWORK_FIREWALL_CHAINS =
                 "com.android.net.flags.metered_network_firewall_chains";
+        static final String BLOCKED_REASON_OEM_DENY_CHAINS =
+                "com.android.net.flags.blocked_reason_oem_deny_chains";
     }
 
     /**
@@ -913,6 +915,19 @@
     public static final int BLOCKED_REASON_APP_BACKGROUND = 1 << 6;
 
     /**
+     * Flag to indicate that an app is subject to OEM-specific application restrictions that would
+     * result in its network access being blocked.
+     *
+     * @see #FIREWALL_CHAIN_OEM_DENY_1
+     * @see #FIREWALL_CHAIN_OEM_DENY_2
+     * @see #FIREWALL_CHAIN_OEM_DENY_3
+     * @hide
+     */
+    @FlaggedApi(Flags.BLOCKED_REASON_OEM_DENY_CHAINS)
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    public static final int BLOCKED_REASON_OEM_DENY = 1 << 7;
+
+    /**
      * Flag to indicate that an app is subject to Data saver restrictions that would
      * result in its metered network access being blocked.
      *
@@ -952,6 +967,7 @@
             BLOCKED_REASON_LOCKDOWN_VPN,
             BLOCKED_REASON_LOW_POWER_STANDBY,
             BLOCKED_REASON_APP_BACKGROUND,
+            BLOCKED_REASON_OEM_DENY,
             BLOCKED_METERED_REASON_DATA_SAVER,
             BLOCKED_METERED_REASON_USER_RESTRICTED,
             BLOCKED_METERED_REASON_ADMIN_DISABLED,
diff --git a/framework/src/android/net/apf/ApfCapabilities.java b/framework/src/android/net/apf/ApfCapabilities.java
index fae2499..8efb182 100644
--- a/framework/src/android/net/apf/ApfCapabilities.java
+++ b/framework/src/android/net/apf/ApfCapabilities.java
@@ -22,6 +22,8 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import java.util.Objects;
+
 /**
  * APF program support capabilities. APF stands for Android Packet Filtering and it is a flexible
  * way to drop unwanted network packets to save power.
@@ -102,6 +104,11 @@
                 && apfPacketFormat == other.apfPacketFormat;
     }
 
+    @Override
+    public int hashCode() {
+        return Objects.hash(apfVersionSupported, maximumApfProgramSize, apfPacketFormat);
+    }
+
     /**
      * Determines whether the APF interpreter advertises support for the data buffer access opcodes
      * LDDW (LoaD Data Word) and STDW (STore Data Word). Full LDDW (LoaD Data Word) and
diff --git a/framework/src/android/net/connectivity/ConnectivityCompatChanges.java b/framework/src/android/net/connectivity/ConnectivityCompatChanges.java
index a80db85..c726dab 100644
--- a/framework/src/android/net/connectivity/ConnectivityCompatChanges.java
+++ b/framework/src/android/net/connectivity/ConnectivityCompatChanges.java
@@ -99,6 +99,28 @@
     @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
     public static final long ENABLE_MATCH_LOCAL_NETWORK = 319212206L;
 
+    /**
+     * On Android {@link android.os.Build.VERSION_CODES.VANILLA_ICE_CREAM} or higher releases,
+     * network access from apps targeting Android 36 or higher that do not have the
+     * {@link android.Manifest.permission#INTERNET} permission is considered blocked.
+     * This results in API behaviors change for apps without
+     * {@link android.Manifest.permission#INTERNET} permission.
+     * {@link android.net.NetworkInfo} returned from {@link android.net.ConnectivityManager} APIs
+     * always has {@link android.net.NetworkInfo.DetailedState#BLOCKED}.
+     * {@link android.net.ConnectivityManager#getActiveNetwork()} always returns null.
+     * {@link android.net.ConnectivityManager.NetworkCallback#onBlockedStatusChanged()} is always
+     * called with blocked=true.
+     * <p>
+     * For backwards compatibility, apps running on older releases, or targeting older SDK levels,
+     * network access from apps without {@link android.Manifest.permission#INTERNET} permission is
+     * considered not blocked even though apps cannot access any networks.
+     *
+     * @hide
+     */
+    @ChangeId
+    @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
+    public static final long NETWORK_BLOCKED_WITHOUT_INTERNET_PERMISSION = 333340911L;
+
     private ConnectivityCompatChanges() {
     }
 }
diff --git a/netbpfload/NetBpfLoad.cpp b/netbpfload/NetBpfLoad.cpp
index 710782d..0e1da93 100644
--- a/netbpfload/NetBpfLoad.cpp
+++ b/netbpfload/NetBpfLoad.cpp
@@ -51,23 +51,22 @@
 #include "bpf/BpfUtils.h"
 #include "loader.h"
 
-using android::base::EndsWith;
-using android::bpf::domain;
+namespace android {
+namespace bpf {
+
+using base::EndsWith;
 using std::string;
 
-bool exists(const char* const path) {
+static bool exists(const char* const path) {
     int v = access(path, F_OK);
-    if (!v) {
-        ALOGI("%s exists.", path);
-        return true;
-    }
+    if (!v) return true;
     if (errno == ENOENT) return false;
     ALOGE("FATAL: access(%s, F_OK) -> %d [%d:%s]", path, v, errno, strerror(errno));
     abort();  // can only hit this if permissions (likely selinux) are screwed up
 }
 
 
-const android::bpf::Location locations[] = {
+const Location locations[] = {
         // S+ Tethering mainline module (network_stack): tether offload
         {
                 .dir = "/apex/com.android.tethering/etc/bpf/",
@@ -97,7 +96,7 @@
         },
 };
 
-int loadAllElfObjects(const unsigned int bpfloader_ver, const android::bpf::Location& location) {
+static int loadAllElfObjects(const unsigned int bpfloader_ver, const Location& location) {
     int retVal = 0;
     DIR* dir;
     struct dirent* ent;
@@ -111,12 +110,12 @@
             progPath += s;
 
             bool critical;
-            int ret = android::bpf::loadProg(progPath.c_str(), &critical, bpfloader_ver, location);
+            int ret = loadProg(progPath.c_str(), &critical, bpfloader_ver, location);
             if (ret) {
                 if (critical) retVal = ret;
                 ALOGE("Failed to load object: %s, ret: %s", progPath.c_str(), std::strerror(-ret));
             } else {
-                ALOGI("Loaded object: %s", progPath.c_str());
+                ALOGD("Loaded object: %s", progPath.c_str());
             }
         }
         closedir(dir);
@@ -124,7 +123,7 @@
     return retVal;
 }
 
-int createSysFsBpfSubDir(const char* const prefix) {
+static int createSysFsBpfSubDir(const char* const prefix) {
     if (*prefix) {
         mode_t prevUmask = umask(0);
 
@@ -147,8 +146,8 @@
 // Technically 'value' doesn't need to be newline terminated, but it's best
 // to include a newline to match 'echo "value" > /proc/sys/...foo' behaviour,
 // which is usually how kernel devs test the actual sysctl interfaces.
-int writeProcSysFile(const char *filename, const char *value) {
-    android::base::unique_fd fd(open(filename, O_WRONLY | O_CLOEXEC));
+static int writeProcSysFile(const char *filename, const char *value) {
+    base::unique_fd fd(open(filename, O_WRONLY | O_CLOEXEC));
     if (fd < 0) {
         const int err = errno;
         ALOGE("open('%s', O_WRONLY | O_CLOEXEC) -> %s", filename, strerror(err));
@@ -172,7 +171,7 @@
 #define APEX_MOUNT_POINT "/apex/com.android.tethering"
 const char * const platformBpfLoader = "/system/bin/bpfloader";
 
-int logTetheringApexVersion(void) {
+static int logTetheringApexVersion(void) {
     char * found_blockdev = NULL;
     FILE * f = NULL;
     char buf[4096];
@@ -198,7 +197,7 @@
     f = NULL;
 
     if (!found_blockdev) return 2;
-    ALOGD("Found Tethering Apex mounted from blockdev %s", found_blockdev);
+    ALOGV("Found Tethering Apex mounted from blockdev %s", found_blockdev);
 
     f = fopen("/proc/mounts", "re");
     if (!f) { free(found_blockdev); return 3; }
@@ -224,12 +223,12 @@
     return 0;
 }
 
-int main(int argc, char** argv, char * const envp[]) {
-    (void)argc;
-    android::base::InitLogging(argv, &android::base::KernelLogger);
+static bool isGSI() {
+    // From //system/gsid/libgsi.cpp IsGsiRunning()
+    return !access("/metadata/gsi/dsu/booted", F_OK);
+}
 
-    ALOGI("NetBpfLoad '%s' starting...", argv[0]);
-
+static int doLoad(char** argv, char * const envp[]) {
     const int device_api_level = android_get_device_api_level();
     const bool isAtLeastT = (device_api_level >= __ANDROID_API_T__);
     const bool isAtLeastU = (device_api_level >= __ANDROID_API_U__);
@@ -240,9 +239,9 @@
     // first in U QPR2 beta~2
     const bool has_platform_netbpfload_rc = exists("/system/etc/init/netbpfload.rc");
 
-    ALOGI("NetBpfLoad api:%d/%d kver:%07x rc:%d%d",
-          android_get_application_target_sdk_version(), device_api_level,
-          android::bpf::kernelVersion(),
+    ALOGI("NetBpfLoad (%s) api:%d/%d kver:%07x (%s) rc:%d%d",
+          argv[0], android_get_application_target_sdk_version(), device_api_level,
+          kernelVersion(), describeArch(),
           has_platform_bpfloader_rc, has_platform_netbpfload_rc);
 
     if (!has_platform_bpfloader_rc && !has_platform_netbpfload_rc) {
@@ -262,27 +261,56 @@
         return 1;
     }
 
-    if (isAtLeastT && !android::bpf::isAtLeastKernelVersion(4, 9, 0)) {
+    if (isAtLeastT && !isAtLeastKernelVersion(4, 9, 0)) {
         ALOGE("Android T requires kernel 4.9.");
         return 1;
     }
 
-    if (isAtLeastU && !android::bpf::isAtLeastKernelVersion(4, 14, 0)) {
+    if (isAtLeastU && !isAtLeastKernelVersion(4, 14, 0)) {
         ALOGE("Android U requires kernel 4.14.");
         return 1;
     }
 
-    if (isAtLeastV && !android::bpf::isAtLeastKernelVersion(4, 19, 0)) {
+    if (isAtLeastV && !isAtLeastKernelVersion(4, 19, 0)) {
         ALOGE("Android V requires kernel 4.19.");
         return 1;
     }
 
-    if (isAtLeastV && android::bpf::isX86() && !android::bpf::isKernel64Bit()) {
+    if (isAtLeastV && isX86() && !isKernel64Bit()) {
         ALOGE("Android V requires X86 kernel to be 64-bit.");
         return 1;
     }
 
-    if (android::bpf::isUserspace32bit() && android::bpf::isAtLeastKernelVersion(6, 2, 0)) {
+    if (isAtLeastV) {
+        bool bad = false;
+
+        if (!isLtsKernel()) {
+            ALOGW("Android V only supports LTS kernels.");
+            bad = true;
+        }
+
+#define REQUIRE(maj, min, sub) \
+        if (isKernelVersion(maj, min) && !isAtLeastKernelVersion(maj, min, sub)) { \
+            ALOGW("Android V requires %d.%d kernel to be %d.%d.%d+.", maj, min, maj, min, sub); \
+            bad = true; \
+        }
+
+        REQUIRE(4, 19, 236)
+        REQUIRE(5, 4, 186)
+        REQUIRE(5, 10, 199)
+        REQUIRE(5, 15, 136)
+        REQUIRE(6, 1, 57)
+        REQUIRE(6, 6, 0)
+
+#undef REQUIRE
+
+        if (bad && !isGSI()) {
+            ALOGE("Unsupported kernel version (%07x).", kernelVersion());
+            sleep(60);
+        }
+    }
+
+    if (isUserspace32bit() && isAtLeastKernelVersion(6, 2, 0)) {
         /* Android 14/U should only launch on 64-bit kernels
          *   T launches on 5.10/5.15
          *   U launches on 5.15/6.1
@@ -307,9 +335,9 @@
     }
 
     // Ensure we can determine the Android build type.
-    if (!android::bpf::isEng() && !android::bpf::isUser() && !android::bpf::isUserdebug()) {
+    if (!isEng() && !isUser() && !isUserdebug()) {
         ALOGE("Failed to determine the build type: got %s, want 'eng', 'user', or 'userdebug'",
-              android::bpf::getBuildType().c_str());
+              getBuildType().c_str());
         return 1;
     }
 
@@ -319,7 +347,7 @@
         // (this writeFile is known to fail on at least 4.19, but always defaults to 0 on
         // pre-5.13, on 5.13+ it depends on CONFIG_BPF_UNPRIV_DEFAULT_OFF)
         if (writeProcSysFile("/proc/sys/kernel/unprivileged_bpf_disabled", "0\n") &&
-            android::bpf::isAtLeastKernelVersion(5, 13, 0)) return 1;
+            isAtLeastKernelVersion(5, 13, 0)) return 1;
     }
 
     if (isAtLeastU) {
@@ -373,9 +401,9 @@
 
     int key = 1;
     int value = 123;
-    android::base::unique_fd map(
-            android::bpf::createMap(BPF_MAP_TYPE_ARRAY, sizeof(key), sizeof(value), 2, 0));
-    if (android::bpf::writeToMapEntry(map, &key, &value, BPF_ANY)) {
+    base::unique_fd map(
+            createMap(BPF_MAP_TYPE_ARRAY, sizeof(key), sizeof(value), 2, 0));
+    if (writeToMapEntry(map, &key, &value, BPF_ANY)) {
         ALOGE("Critical kernel bug - failure to write into index 1 of 2 element bpf map array.");
         return 1;
     }
@@ -392,3 +420,21 @@
     ALOGI("mainline done!");
     return 0;
 }
+
+}  // namespace bpf
+}  // namespace android
+
+int main(int argc, char** argv, char * const envp[]) {
+    android::base::InitLogging(argv, &android::base::KernelLogger);
+
+    if (argc == 2 && !strcmp(argv[1], "done")) {
+        // we're being re-exec'ed from platform bpfloader to 'finalize' things
+        if (!android::base::SetProperty("bpf.progs_loaded", "1")) {
+            ALOGE("Failed to set bpf.progs_loaded property to 1.");
+            return 125;
+        }
+        return 0;
+    }
+
+    return android::bpf::doLoad(argv, envp);
+}
diff --git a/netbpfload/loader.cpp b/netbpfload/loader.cpp
index 52428a3..2b5f5c7 100644
--- a/netbpfload/loader.cpp
+++ b/netbpfload/loader.cpp
@@ -512,7 +512,7 @@
 
         ret = readSectionByIdx(elfFile, i, cs_temp.data);
         if (ret) return ret;
-        ALOGD("Loaded code section %d (%s)", i, name.c_str());
+        ALOGV("Loaded code section %d (%s)", i, name.c_str());
 
         vector<string> csSymNames;
         ret = getSectionSymNames(elfFile, oldName, csSymNames, STT_FUNC);
@@ -532,13 +532,13 @@
             if (name == (".rel" + oldName)) {
                 ret = readSectionByIdx(elfFile, i + 1, cs_temp.rel_data);
                 if (ret) return ret;
-                ALOGD("Loaded relo section %d (%s)", i, name.c_str());
+                ALOGV("Loaded relo section %d (%s)", i, name.c_str());
             }
         }
 
         if (cs_temp.data.size() > 0) {
             cs.push_back(std::move(cs_temp));
-            ALOGD("Adding section %d to cs list", i);
+            ALOGV("Adding section %d to cs list", i);
         }
     }
     return 0;
@@ -869,7 +869,7 @@
 
     // Occasionally might be useful for relocation debugging, but pretty spammy
     if (0) {
-        ALOGD("applying relo to instruction at byte offset: %llu, "
+        ALOGV("applying relo to instruction at byte offset: %llu, "
               "insn offset %d, insn %llx",
               (unsigned long long)offset, insnIndex, *(unsigned long long*)insn);
     }
@@ -1177,7 +1177,7 @@
     }
 
     for (int i = 0; i < (int)mapFds.size(); i++)
-        ALOGD("map_fd found at %d is %d in %s", i, mapFds[i].get(), elfPath);
+        ALOGV("map_fd found at %d is %d in %s", i, mapFds[i].get(), elfPath);
 
     applyMapRelo(elfFile, mapFds, cs);
 
diff --git a/service-t/src/com/android/server/IpSecService.java b/service-t/src/com/android/server/IpSecService.java
index ea91e64..54b9ced 100644
--- a/service-t/src/com/android/server/IpSecService.java
+++ b/service-t/src/com/android/server/IpSecService.java
@@ -1877,6 +1877,10 @@
         mContext.enforceCallingOrSelfPermission(
                 android.Manifest.permission.ACCESS_NETWORK_STATE, "IpsecService#getTransformState");
 
+        if (transformId == INVALID_RESOURCE_ID) {
+            throw new IllegalStateException("This transform is already closed");
+        }
+
         UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
         TransformRecord transformInfo =
                 userRecord.mTransformRecords.getResourceOrThrow(transformId);
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 39e8bcc..36f3982 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsRecordRepository.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsRecordRepository.java
@@ -34,6 +34,7 @@
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.SparseArray;
+import android.util.SparseIntArray;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.net.module.util.CollectionUtils;
@@ -87,6 +88,12 @@
     private static final String[] DNS_SD_SERVICE_TYPE =
             new String[] { "_services", "_dns-sd", "_udp", LOCAL_TLD };
 
+    private enum RecordConflictType {
+        NO_CONFLICT,
+        CONFLICT,
+        IDENTICAL
+    }
+
     @NonNull
     private final Random mDelayGenerator = new Random();
     // Map of service unique ID -> records for service
@@ -1172,38 +1179,49 @@
      * {@link MdnsInterfaceAdvertiser#CONFLICT_HOST}.
      */
     public Map<Integer, Integer> getConflictingServices(MdnsPacket packet) {
-        // Avoid allocating a new set for each incoming packet: use an empty set by default.
-        Map<Integer, Integer> conflicting = Collections.emptyMap();
+        Map<Integer, Integer> conflicting = new ArrayMap<>();
         for (MdnsRecord record : packet.answers) {
+            SparseIntArray conflictingWithRecord = new SparseIntArray();
             for (int i = 0; i < mServices.size(); i++) {
                 final ServiceRegistration registration = mServices.valueAt(i);
                 if (registration.exiting) continue;
 
-                int conflictType = 0;
+                final RecordConflictType conflictForService =
+                        conflictForService(record, registration);
+                final RecordConflictType conflictForHost = conflictForHost(record, registration);
 
-                if (conflictForService(record, registration)) {
-                    conflictType |= CONFLICT_SERVICE;
+                // Identical record is found in the repository so there won't be a conflict.
+                if (conflictForService == RecordConflictType.IDENTICAL
+                        || conflictForHost == RecordConflictType.IDENTICAL) {
+                    conflictingWithRecord.clear();
+                    break;
                 }
 
-                if (conflictForHost(record, registration)) {
+                int conflictType = 0;
+                if (conflictForService == RecordConflictType.CONFLICT) {
+                    conflictType |= CONFLICT_SERVICE;
+                }
+                if (conflictForHost == RecordConflictType.CONFLICT) {
                     conflictType |= CONFLICT_HOST;
                 }
 
                 if (conflictType != 0) {
-                    if (conflicting.isEmpty()) {
-                        // Conflict was found: use a mutable set
-                        conflicting = new ArrayMap<>();
-                    }
                     final int serviceId = mServices.keyAt(i);
-                    conflicting.put(serviceId, conflictType);
+                    conflictingWithRecord.put(serviceId, conflictType);
                 }
             }
+            for (int i = 0; i < conflictingWithRecord.size(); i++) {
+                final int serviceId = conflictingWithRecord.keyAt(i);
+                final int conflictType = conflictingWithRecord.valueAt(i);
+                final int oldConflictType = conflicting.getOrDefault(serviceId, 0);
+                conflicting.put(serviceId, oldConflictType | conflictType);
+            }
         }
 
         return conflicting;
     }
 
-    private static boolean conflictForService(
+    private static RecordConflictType conflictForService(
             @NonNull MdnsRecord record, @NonNull ServiceRegistration registration) {
         String[] fullServiceName;
         if (registration.srvRecord != null) {
@@ -1211,75 +1229,75 @@
         } else if (registration.serviceKeyRecord != null) {
             fullServiceName = registration.serviceKeyRecord.record.getName();
         } else {
-            return false;
+            return RecordConflictType.NO_CONFLICT;
         }
 
         if (!MdnsUtils.equalsDnsLabelIgnoreDnsCase(record.getName(), fullServiceName)) {
-            return false;
+            return RecordConflictType.NO_CONFLICT;
         }
 
         // As per RFC6762 9., it's fine if the "conflict" is an identical record with same
         // data.
         if (record instanceof MdnsServiceRecord && equals(record, registration.srvRecord)) {
-            return false;
+            return RecordConflictType.IDENTICAL;
         }
         if (record instanceof MdnsTextRecord && equals(record, registration.txtRecord)) {
-            return false;
+            return RecordConflictType.IDENTICAL;
         }
         if (record instanceof MdnsKeyRecord && equals(record, registration.serviceKeyRecord)) {
-            return false;
+            return RecordConflictType.IDENTICAL;
         }
 
-        return true;
+        return RecordConflictType.CONFLICT;
     }
 
-    private boolean conflictForHost(
+    private RecordConflictType conflictForHost(
             @NonNull MdnsRecord record, @NonNull ServiceRegistration registration) {
         // Only custom hosts are checked. When using the default host, the hostname is derived from
         // a UUID and it's supposed to be unique.
         if (registration.serviceInfo.getHostname() == null) {
-            return false;
+            return RecordConflictType.NO_CONFLICT;
         }
 
-        // It cannot be a hostname conflict because not record is registered with the hostname.
+        // It cannot be a hostname conflict because no record is registered with the hostname.
         if (registration.addressRecords.isEmpty() && registration.hostKeyRecord == null) {
-            return false;
+            return RecordConflictType.NO_CONFLICT;
         }
 
         // The record's name cannot be registered by NsdManager so it's not a conflict.
         if (record.getName().length != 2 || !record.getName()[1].equals(LOCAL_TLD)) {
-            return false;
+            return RecordConflictType.NO_CONFLICT;
         }
 
         // Different names. There won't be a conflict.
         if (!MdnsUtils.equalsIgnoreDnsCase(
                 record.getName()[0], registration.serviceInfo.getHostname())) {
-            return false;
+            return RecordConflictType.NO_CONFLICT;
         }
 
         // As per RFC6762 9., it's fine if the "conflict" is an identical record with same
         // data.
         if (record instanceof MdnsInetAddressRecord
                 && hasInetAddressRecord(registration, (MdnsInetAddressRecord) record)) {
-            return false;
+            return RecordConflictType.IDENTICAL;
         }
         if (record instanceof MdnsKeyRecord && equals(record, registration.hostKeyRecord)) {
-            return false;
+            return RecordConflictType.IDENTICAL;
         }
 
         // Per RFC 6762 8.1, when a record is being probed, any answer containing a record with that
         // name, of any type, MUST be considered a conflicting response.
         if (registration.isProbing) {
-            return true;
+            return RecordConflictType.CONFLICT;
         }
         if (record instanceof MdnsInetAddressRecord && !registration.addressRecords.isEmpty()) {
-            return true;
+            return RecordConflictType.CONFLICT;
         }
         if (record instanceof MdnsKeyRecord && registration.hostKeyRecord != null) {
-            return true;
+            return RecordConflictType.CONFLICT;
         }
 
-        return false;
+        return RecordConflictType.NO_CONFLICT;
     }
 
     private List<RecordInfo<MdnsInetAddressRecord>> getInetAddressRecordsForHostname(
diff --git a/service-t/src/com/android/server/ethernet/EthernetServiceImpl.java b/service-t/src/com/android/server/ethernet/EthernetServiceImpl.java
index 92f1953..b8689d6 100644
--- a/service-t/src/com/android/server/ethernet/EthernetServiceImpl.java
+++ b/service-t/src/com/android/server/ethernet/EthernetServiceImpl.java
@@ -108,11 +108,7 @@
             PermissionUtils.enforceRestrictedNetworkPermission(mContext, TAG);
         }
 
-        // This causes thread-unsafe access on mIpConfigurations which might
-        // race with calls to EthernetManager#updateConfiguration().
-        // EthernetManager#getConfiguration() has been marked as
-        // @UnsupportedAppUsage since Android R.
-        return mTracker.getIpConfiguration(iface);
+        return new IpConfiguration(mTracker.getIpConfiguration(iface));
     }
 
     /**
diff --git a/service-t/src/com/android/server/ethernet/EthernetTracker.java b/service-t/src/com/android/server/ethernet/EthernetTracker.java
index a60592f..71f289e 100644
--- a/service-t/src/com/android/server/ethernet/EthernetTracker.java
+++ b/service-t/src/com/android/server/ethernet/EthernetTracker.java
@@ -31,6 +31,8 @@
 import android.net.ITetheredInterfaceCallback;
 import android.net.InterfaceConfigurationParcel;
 import android.net.IpConfiguration;
+import android.net.IpConfiguration.IpAssignment;
+import android.net.IpConfiguration.ProxySettings;
 import android.net.LinkAddress;
 import android.net.NetworkCapabilities;
 import android.net.StaticIpConfiguration;
@@ -109,7 +111,6 @@
     /** Mapping between {iface name | mac address} -> {NetworkCapabilities} */
     private final ConcurrentHashMap<String, NetworkCapabilities> mNetworkCapabilities =
             new ConcurrentHashMap<>();
-    /** Mapping between {iface name | mac address} -> {IpConfiguration} */
     private final ConcurrentHashMap<String, IpConfiguration> mIpConfigurations =
             new ConcurrentHashMap<>();
 
@@ -297,7 +298,7 @@
     }
 
     private IpConfiguration getIpConfigurationForCallback(String iface, int state) {
-        return (state == EthernetManager.STATE_ABSENT) ? null : getIpConfiguration(iface);
+        return (state == EthernetManager.STATE_ABSENT) ? null : getOrCreateIpConfiguration(iface);
     }
 
     private void ensureRunningOnEthernetServiceThread() {
@@ -390,83 +391,8 @@
         mHandler.post(() -> setInterfaceAdministrativeState(iface, enabled, cb));
     }
 
-    private @Nullable String getHwAddress(String iface) {
-        if (getInterfaceRole(iface) == EthernetManager.ROLE_SERVER) {
-            return mTetheringInterfaceHwAddr;
-        }
-
-        return mFactory.getHwAddress(iface);
-    }
-
-    /**
-     * Get the IP configuration of the interface, or the default if the interface doesn't exist.
-     * @param iface the name of the interface to retrieve.
-     *
-     * @return The IP configuration
-     */
-    public IpConfiguration getIpConfiguration(String iface) {
-        return getIpConfiguration(iface, getHwAddress(iface));
-    }
-
-    private IpConfiguration getIpConfiguration(String iface, @Nullable String hwAddress) {
-        // Look up Ip configuration first by ifname, then by MAC address.
-        IpConfiguration ipConfig = mIpConfigurations.get(iface);
-        if (ipConfig != null) {
-            return ipConfig;
-        }
-
-        if (hwAddress == null) {
-            // should never happen.
-            Log.wtf(TAG, "No hardware address for interface " + iface);
-        } else {
-            ipConfig = mIpConfigurations.get(hwAddress);
-        }
-
-        if (ipConfig == null) {
-            ipConfig = new IpConfiguration.Builder().build();
-        }
-
-        return ipConfig;
-    }
-
-    private NetworkCapabilities getNetworkCapabilities(String iface) {
-        return getNetworkCapabilities(iface, getHwAddress(iface));
-    }
-
-    private NetworkCapabilities getNetworkCapabilities(String iface, @Nullable String hwAddress) {
-        // Look up network capabilities first by ifname, then by MAC address.
-        NetworkCapabilities networkCapabilities = mNetworkCapabilities.get(iface);
-        if (networkCapabilities != null) {
-            return networkCapabilities;
-        }
-
-        if (hwAddress == null) {
-            // should never happen.
-            Log.wtf(TAG, "No hardware address for interface " + iface);
-        } else {
-            networkCapabilities = mNetworkCapabilities.get(hwAddress);
-        }
-
-        if (networkCapabilities != null) {
-            return networkCapabilities;
-        }
-
-        final NetworkCapabilities.Builder builder = createNetworkCapabilities(
-                false /* clear default capabilities */, null, null)
-                .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
-                .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED)
-                .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING)
-                .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED)
-                .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED)
-                .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED);
-
-        if (isValidTestInterface(iface)) {
-            builder.addTransportType(NetworkCapabilities.TRANSPORT_TEST);
-        } else {
-            builder.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
-        }
-
-        return builder.build();
+    IpConfiguration getIpConfiguration(String iface) {
+        return mIpConfigurations.get(iface);
     }
 
     @VisibleForTesting(visibility = PACKAGE)
@@ -507,8 +433,8 @@
      * NET_CAPABILITY_NOT_RESTRICTED) capability. Otherwise, returns false.
      */
     boolean isRestrictedInterface(String iface) {
-        final NetworkCapabilities nc = getNetworkCapabilities(iface);
-        return !nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
+        final NetworkCapabilities nc = mNetworkCapabilities.get(iface);
+        return nc != null && !nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
     }
 
     void addListener(IEthernetServiceListener listener, boolean canUseRestrictedNetworks) {
@@ -697,9 +623,17 @@
             return;
         }
 
-        final NetworkCapabilities nc = getNetworkCapabilities(iface, hwAddress);
-        final IpConfiguration ipConfiguration = getIpConfiguration(iface, hwAddress);
+        NetworkCapabilities nc = mNetworkCapabilities.get(iface);
+        if (nc == null) {
+            // Try to resolve using mac address
+            nc = mNetworkCapabilities.get(hwAddress);
+            if (nc == null) {
+                final boolean isTestIface = iface.matches(TEST_IFACE_REGEXP);
+                nc = createDefaultNetworkCapabilities(isTestIface);
+            }
+        }
 
+        IpConfiguration ipConfiguration = getOrCreateIpConfiguration(iface);
         Log.d(TAG, "Tracking interface in client mode: " + iface);
         mFactory.addInterface(iface, hwAddress, ipConfiguration, nc);
 
@@ -839,6 +773,25 @@
         return new EthernetTrackerConfig(configString.split(";", /* limit of tokens */ 4));
     }
 
+    private static NetworkCapabilities createDefaultNetworkCapabilities(boolean isTestIface) {
+        NetworkCapabilities.Builder builder = createNetworkCapabilities(
+                false /* clear default capabilities */, null, null)
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED)
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING)
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED)
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED)
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED);
+
+        if (isTestIface) {
+            builder.addTransportType(NetworkCapabilities.TRANSPORT_TEST);
+        } else {
+            builder.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
+        }
+
+        return builder.build();
+    }
+
     /**
      * Parses a static list of network capabilities
      *
@@ -973,6 +926,15 @@
         return new IpConfiguration.Builder().setStaticIpConfiguration(staticIpConfig).build();
     }
 
+    private IpConfiguration getOrCreateIpConfiguration(String iface) {
+        IpConfiguration ret = mIpConfigurations.get(iface);
+        if (ret != null) return ret;
+        ret = new IpConfiguration();
+        ret.setIpAssignment(IpAssignment.DHCP);
+        ret.setProxySettings(ProxySettings.NONE);
+        return ret;
+    }
+
     private boolean isValidEthernetInterface(String iface) {
         return iface.matches(mIfaceMatch) || isValidTestInterface(iface);
     }
@@ -1059,7 +1021,7 @@
             pw.println("IP Configurations:");
             pw.increaseIndent();
             for (String iface : mIpConfigurations.keySet()) {
-                pw.println(iface + ": " + getIpConfiguration(iface));
+                pw.println(iface + ": " + mIpConfigurations.get(iface));
             }
             pw.decreaseIndent();
             pw.println();
@@ -1067,7 +1029,7 @@
             pw.println("Network Capabilities:");
             pw.increaseIndent();
             for (String iface : mNetworkCapabilities.keySet()) {
-                pw.println(iface + ": " + getNetworkCapabilities(iface));
+                pw.println(iface + ": " + mNetworkCapabilities.get(iface));
             }
             pw.decreaseIndent();
             pw.println();
diff --git a/service/Android.bp b/service/Android.bp
index 1d74efc..1dd09a9 100644
--- a/service/Android.bp
+++ b/service/Android.bp
@@ -188,7 +188,7 @@
         "androidx.annotation_annotation",
         "connectivity-net-module-utils-bpf",
         "connectivity_native_aidl_interface-lateststable-java",
-        "dnsresolver_aidl_interface-V14-java",
+        "dnsresolver_aidl_interface-V15-java",
         "modules-utils-shell-command-handler",
         "net-utils-device-common",
         "net-utils-device-common-ip",
diff --git a/service/src/com/android/server/BpfNetMaps.java b/service/src/com/android/server/BpfNetMaps.java
index 04d8ea4..23af0f8 100644
--- a/service/src/com/android/server/BpfNetMaps.java
+++ b/service/src/com/android/server/BpfNetMaps.java
@@ -32,6 +32,8 @@
 import static android.net.BpfNetMapsUtils.getMatchByFirewallChain;
 import static android.net.BpfNetMapsUtils.isFirewallAllowList;
 import static android.net.BpfNetMapsUtils.matchToString;
+import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_MASK;
+import static android.net.ConnectivityManager.BLOCKED_REASON_NONE;
 import static android.net.ConnectivityManager.FIREWALL_RULE_ALLOW;
 import static android.net.ConnectivityManager.FIREWALL_RULE_DENY;
 import static android.net.INetd.PERMISSION_INTERNET;
@@ -804,6 +806,25 @@
     }
 
     /**
+     * Get granted permissions for specified uid. If uid is not in the map, this method returns
+     * {@link android.net.INetd.PERMISSION_INTERNET} since this is a default permission.
+     * See {@link #setNetPermForUids}
+     *
+     * @param uid target uid
+     * @return    granted permissions.
+     */
+    @RequiresApi(Build.VERSION_CODES.TIRAMISU)
+    public int getNetPermForUid(final int uid) {
+        try {
+            final U8 permissions = sUidPermissionMap.getValue(new S32(uid));
+            return permissions != null ? permissions.val : PERMISSION_INTERNET;
+        } catch (ErrnoException e) {
+            Log.wtf(TAG, "Failed to get permission for uid: " + uid);
+            return PERMISSION_INTERNET;
+        }
+    }
+
+    /**
      * Set Data Saver enabled or disabled
      *
      * @param enable     whether Data Saver is enabled or disabled.
@@ -863,6 +884,49 @@
         }
     }
 
+    /**
+     * Get blocked reasons for specified uid
+     *
+     * @param uid Target Uid
+     * @return Reasons of network access blocking for an UID
+     */
+    @RequiresApi(Build.VERSION_CODES.TIRAMISU)
+    public int getUidNetworkingBlockedReasons(final int uid) {
+        return BpfNetMapsUtils.getUidNetworkingBlockedReasons(uid,
+                sConfigurationMap, sUidOwnerMap, sDataSaverEnabledMap);
+    }
+
+    /**
+     * Return whether the network access of specified uid is blocked on metered networks
+     *
+     * @param uid The target uid.
+     * @return True if the network access is blocked on metered networks. Otherwise, false
+     */
+    @RequiresApi(Build.VERSION_CODES.TIRAMISU)
+    public boolean isUidRestrictedOnMeteredNetworks(final int uid) {
+        final int blockedReasons = getUidNetworkingBlockedReasons(uid);
+        return (blockedReasons & BLOCKED_METERED_REASON_MASK) != BLOCKED_REASON_NONE;
+    }
+
+    /*
+     * Return whether the network is blocked by firewall chains for the given uid.
+     *
+     * Note that {@link #getDataSaverEnabled()} has a latency before V.
+     *
+     * @param uid The target uid.
+     * @param isNetworkMetered Whether the target network is metered.
+     *
+     * @return True if the network is blocked. Otherwise, false.
+     * @throws ServiceSpecificException if the read fails.
+     *
+     * @hide
+     */
+    @RequiresApi(Build.VERSION_CODES.TIRAMISU)
+    public boolean isUidNetworkingBlocked(final int uid, boolean isNetworkMetered) {
+        return BpfNetMapsUtils.isUidNetworkingBlocked(uid, isNetworkMetered,
+                sConfigurationMap, sUidOwnerMap, sDataSaverEnabledMap);
+    }
+
     /** Register callback for statsd to pull atom. */
     @RequiresApi(Build.VERSION_CODES.TIRAMISU)
     public void setPullAtomCallback(final Context context) {
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index dc91f7d..fff809e 100755
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -64,6 +64,7 @@
 import static android.net.ConnectivityManager.getNetworkTypeName;
 import static android.net.ConnectivityManager.isNetworkTypeValid;
 import static android.net.ConnectivitySettingsManager.PRIVATE_DNS_MODE_OPPORTUNISTIC;
+import static android.net.INetd.PERMISSION_INTERNET;
 import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_PRIVDNS;
 import static android.net.INetworkMonitor.NETWORK_VALIDATION_RESULT_PARTIAL;
 import static android.net.INetworkMonitor.NETWORK_VALIDATION_RESULT_SKIPPED;
@@ -101,6 +102,7 @@
 import static android.net.OemNetworkPreferences.OEM_NETWORK_PREFERENCE_TEST_ONLY;
 import static android.net.connectivity.ConnectivityCompatChanges.ENABLE_MATCH_LOCAL_NETWORK;
 import static android.net.connectivity.ConnectivityCompatChanges.ENABLE_SELF_CERTIFIED_CAPABILITIES_DECLARATION;
+import static android.net.connectivity.ConnectivityCompatChanges.NETWORK_BLOCKED_WITHOUT_INTERNET_PERMISSION;
 import static android.os.Process.INVALID_UID;
 import static android.os.Process.VPN_UID;
 import static android.system.OsConstants.ETH_P_ALL;
@@ -2229,6 +2231,11 @@
         return nai;
     }
 
+    @RequiresApi(Build.VERSION_CODES.TIRAMISU)
+    private boolean hasInternetPermission(final int uid) {
+        return (mBpfNetMaps.getNetPermForUid(uid) & PERMISSION_INTERNET) != 0;
+    }
+
     /**
      * Check if UID should be blocked from using the specified network.
      */
@@ -2242,7 +2249,17 @@
         final long ident = Binder.clearCallingIdentity();
         try {
             final boolean metered = nc == null ? true : nc.isMetered();
-            return mPolicyManager.isUidNetworkingBlocked(uid, metered);
+            if (mDeps.isAtLeastV()) {
+                final boolean hasInternetPermission = hasInternetPermission(uid);
+                final boolean blockedByUidRules = mBpfNetMaps.isUidNetworkingBlocked(uid, metered);
+                if (mDeps.isChangeEnabled(NETWORK_BLOCKED_WITHOUT_INTERNET_PERMISSION, uid)) {
+                    return blockedByUidRules || !hasInternetPermission;
+                } else {
+                    return hasInternetPermission && blockedByUidRules;
+                }
+            } else {
+                return mPolicyManager.isUidNetworkingBlocked(uid, metered);
+            }
         } finally {
             Binder.restoreCallingIdentity(ident);
         }
@@ -2320,7 +2337,7 @@
         final int uid = mDeps.getCallingUid();
         final NetworkAgentInfo nai = getNetworkAgentInfoForUid(uid);
         if (nai == null) return null;
-        final NetworkInfo networkInfo = getFilteredNetworkInfo(nai, uid, false);
+        final NetworkInfo networkInfo = getFilteredNetworkInfo(nai, uid, false /* ignoreBlocked */);
         maybeLogBlockedNetworkInfo(networkInfo, uid);
         return networkInfo;
     }
@@ -7953,6 +7970,13 @@
             // Policy already enforced.
             return;
         }
+        if (mDeps.isAtLeastV()) {
+            if (mBpfNetMaps.isUidRestrictedOnMeteredNetworks(uid)) {
+                // If UID is restricted, don't allow them to bring up metered APNs.
+                networkCapabilities.addCapability(NET_CAPABILITY_NOT_METERED);
+            }
+            return;
+        }
         final long ident = Binder.clearCallingIdentity();
         try {
             if (mPolicyManager.isUidRestrictedOnMeteredNetworks(uid)) {
@@ -12737,8 +12761,8 @@
         if (um.isManagedProfile(profile.getIdentifier())) {
             return true;
         }
-        if (mDeps.isAtLeastT() && dpm.getDeviceOwner() != null) return true;
-        return false;
+
+        return mDeps.isAtLeastT() && dpm.getDeviceOwnerComponentOnAnyUser() != null;
     }
 
     /**
diff --git a/service/src/com/android/server/connectivity/ClatCoordinator.java b/service/src/com/android/server/connectivity/ClatCoordinator.java
index eea16bf..634a8fa 100644
--- a/service/src/com/android/server/connectivity/ClatCoordinator.java
+++ b/service/src/com/android/server/connectivity/ClatCoordinator.java
@@ -399,6 +399,10 @@
         mCookieTagMap = mDeps.getBpfCookieTagMap();
     }
 
+    // Note that this may only be called on a brand new v4-* interface,
+    // because it uses bpfmap.insertEntry() which fails if entry exists,
+    // and because the value includes (initialized to 0) byte/packet
+    // counters, so a replace (instead of insert) would wipe those stats.
     private void maybeStartBpf(final ClatdTracker tracker) {
         if (mIngressMap == null || mEgressMap == null) return;
 
@@ -520,31 +524,6 @@
         }
     }
 
-    private void maybeCleanUp(ParcelFileDescriptor tunFd, ParcelFileDescriptor readSock6,
-            ParcelFileDescriptor writeSock6) {
-        if (tunFd != null) {
-            try {
-                tunFd.close();
-            } catch (IOException e) {
-                Log.e(TAG, "Fail to close tun file descriptor " + e);
-            }
-        }
-        if (readSock6 != null) {
-            try {
-                readSock6.close();
-            } catch (IOException e) {
-                Log.e(TAG, "Fail to close read socket " + e);
-            }
-        }
-        if (writeSock6 != null) {
-            try {
-                writeSock6.close();
-            } catch (IOException e) {
-                Log.e(TAG, "Fail to close write socket " + e);
-            }
-        }
-    }
-
     private void tagSocketAsClat(long cookie) throws IOException {
         if (mCookieTagMap == null) {
             throw new IOException("Cookie tag map is not initialized");
@@ -600,42 +579,6 @@
             throw new IOException("Prefix must be 96 bits long: " + nat64Prefix);
         }
 
-        // [1] Pick an IPv4 address from 192.0.0.4, 192.0.0.5, 192.0.0.6 ..
-        final String v4Str;
-        try {
-            v4Str = mDeps.selectIpv4Address(INIT_V4ADDR_STRING, INIT_V4ADDR_PREFIX_LEN);
-        } catch (IOException e) {
-            throw new IOException("no IPv4 addresses were available for clat: " + e);
-        }
-
-        final Inet4Address v4;
-        try {
-            v4 = (Inet4Address) InetAddresses.parseNumericAddress(v4Str);
-        } catch (ClassCastException | IllegalArgumentException | NullPointerException e) {
-            throw new IOException("Invalid IPv4 address " + v4Str);
-        }
-
-        // [2] Generate a checksum-neutral IID.
-        final Integer fwmark = getFwmark(netId);
-        final String pfx96Str = nat64Prefix.getAddress().getHostAddress();
-        final String v6Str;
-        try {
-            v6Str = mDeps.generateIpv6Address(iface, v4Str, pfx96Str, fwmark);
-        } catch (IOException e) {
-            throw new IOException("no IPv6 addresses were available for clat: " + e);
-        }
-
-        final Inet6Address pfx96 = (Inet6Address) nat64Prefix.getAddress();
-        final Inet6Address v6;
-        try {
-            v6 = (Inet6Address) InetAddresses.parseNumericAddress(v6Str);
-        } catch (ClassCastException | IllegalArgumentException | NullPointerException e) {
-            throw new IOException("Invalid IPv6 address " + v6Str);
-        }
-
-        // [3] Open, configure and bring up the tun interface.
-        // Create the v4-... tun interface.
-
         // Initialize all required file descriptors with null pointer. This makes the following
         // error handling easier. Simply always call #maybeCleanUp for closing file descriptors,
         // if any valid ones, in error handling.
@@ -643,145 +586,112 @@
         ParcelFileDescriptor readSock6 = null;
         ParcelFileDescriptor writeSock6 = null;
 
-        final String tunIface = CLAT_PREFIX + iface;
-        try {
-            tunFd = mDeps.adoptFd(mDeps.createTunInterface(tunIface));
-        } catch (IOException e) {
-            throw new IOException("Create tun interface " + tunIface + " failed: " + e);
-        }
+        long cookie = 0;
+        boolean isSocketTagged = false;
 
-        final int tunIfIndex = mDeps.getInterfaceIndex(tunIface);
-        if (tunIfIndex == INVALID_IFINDEX) {
-            maybeCleanUp(tunFd, readSock6, writeSock6);
-            throw new IOException("Fail to get interface index for interface " + tunIface);
-        }
+        try {
+            // [1] Pick an IPv4 address from 192.0.0.4, 192.0.0.5, 192.0.0.6 ..
+            final String v4Str = mDeps.selectIpv4Address(INIT_V4ADDR_STRING,
+                    INIT_V4ADDR_PREFIX_LEN);
+            final Inet4Address v4 = (Inet4Address) InetAddresses.parseNumericAddress(v4Str);
 
-        // disable IPv6 on it - failing to do so is not a critical error
-        try {
-            mNetd.interfaceSetEnableIPv6(tunIface, false /* enabled */);
-        } catch (RemoteException | ServiceSpecificException e) {
-            Log.e(TAG, "Disable IPv6 on " + tunIface + " failed: " + e);
-        }
+            // [2] Generate a checksum-neutral IID.
+            final Integer fwmark = getFwmark(netId);
+            final String pfx96Str = nat64Prefix.getAddress().getHostAddress();
+            final String v6Str = mDeps.generateIpv6Address(iface, v4Str, pfx96Str, fwmark);
+            final Inet6Address pfx96 = (Inet6Address) nat64Prefix.getAddress();
+            final Inet6Address v6 = (Inet6Address) InetAddresses.parseNumericAddress(v6Str);
 
-        // Detect ipv4 mtu.
-        final int detectedMtu;
-        try {
-            detectedMtu = mDeps.detectMtu(pfx96Str,
-                ByteBuffer.wrap(GOOGLE_DNS_4.getAddress()).getInt(), fwmark);
-        } catch (IOException e) {
-            maybeCleanUp(tunFd, readSock6, writeSock6);
-            throw new IOException("Detect MTU on " + tunIface + " failed: " + e);
-        }
-        final int mtu = adjustMtu(detectedMtu);
-        Log.i(TAG, "detected ipv4 mtu of " + detectedMtu + " adjusted to " + mtu);
+            // [3] Open and configure local 464xlat read/write sockets.
+            // Opens a packet socket to receive IPv6 packets in clatd.
 
-        // Config tun interface mtu, address and bring up.
-        try {
-            mNetd.interfaceSetMtu(tunIface, mtu);
-        } catch (RemoteException | ServiceSpecificException e) {
-            maybeCleanUp(tunFd, readSock6, writeSock6);
-            throw new IOException("Set MTU " + mtu + " on " + tunIface + " failed: " + e);
-        }
-        final InterfaceConfigurationParcel ifConfig = new InterfaceConfigurationParcel();
-        ifConfig.ifName = tunIface;
-        ifConfig.ipv4Addr = v4Str;
-        ifConfig.prefixLength = 32;
-        ifConfig.hwAddr = "";
-        ifConfig.flags = new String[] {IF_STATE_UP};
-        try {
-            mNetd.interfaceSetCfg(ifConfig);
-        } catch (RemoteException | ServiceSpecificException e) {
-            maybeCleanUp(tunFd, readSock6, writeSock6);
-            throw new IOException("Setting IPv4 address to " + ifConfig.ipv4Addr + "/"
-                    + ifConfig.prefixLength + " failed on " + ifConfig.ifName + ": " + e);
-        }
-
-        // [4] Open and configure local 464xlat read/write sockets.
-        // Opens a packet socket to receive IPv6 packets in clatd.
-        try {
             // Use a JNI call to get native file descriptor instead of Os.socket() because we would
             // like to use ParcelFileDescriptor to manage file descriptor. But ctor
             // ParcelFileDescriptor(FileDescriptor fd) is a @hide function. Need to use native file
             // descriptor to initialize ParcelFileDescriptor object instead.
             readSock6 = mDeps.adoptFd(mDeps.openPacketSocket());
-        } catch (IOException e) {
-            maybeCleanUp(tunFd, readSock6, writeSock6);
-            throw new IOException("Open packet socket failed: " + e);
-        }
 
-        // Opens a raw socket with a given fwmark to send IPv6 packets in clatd.
-        try {
+            // Opens a raw socket with a given fwmark to send IPv6 packets in clatd.
             // Use a JNI call to get native file descriptor instead of Os.socket(). See above
             // reason why we use jniOpenPacketSocket6().
             writeSock6 = mDeps.adoptFd(mDeps.openRawSocket6(fwmark));
-        } catch (IOException e) {
-            maybeCleanUp(tunFd, readSock6, writeSock6);
-            throw new IOException("Open raw socket failed: " + e);
-        }
 
-        final int ifIndex = mDeps.getInterfaceIndex(iface);
-        if (ifIndex == INVALID_IFINDEX) {
-            maybeCleanUp(tunFd, readSock6, writeSock6);
-            throw new IOException("Fail to get interface index for interface " + iface);
-        }
+            final int ifIndex = mDeps.getInterfaceIndex(iface);
+            if (ifIndex == INVALID_IFINDEX) {
+                throw new IOException("Fail to get interface index for interface " + iface);
+            }
 
-        // Start translating packets to the new prefix.
-        try {
+            // Start translating packets to the new prefix.
             mDeps.addAnycastSetsockopt(writeSock6.getFileDescriptor(), v6Str, ifIndex);
-        } catch (IOException e) {
-            maybeCleanUp(tunFd, readSock6, writeSock6);
-            throw new IOException("add anycast sockopt failed: " + e);
-        }
-
-        // Tag socket as AID_CLAT to avoid duplicated CLAT data usage accounting.
-        final long cookie;
-        try {
+            // Tag socket as AID_CLAT to avoid duplicated CLAT data usage accounting.
             cookie = mDeps.getSocketCookie(writeSock6.getFileDescriptor());
             tagSocketAsClat(cookie);
-        } catch (IOException e) {
-            maybeCleanUp(tunFd, readSock6, writeSock6);
-            throw new IOException("tag raw socket failed: " + e);
-        }
-
-        // Update our packet socket filter to reflect the new 464xlat IP address.
-        try {
+            isSocketTagged = true;
+            // Update our packet socket filter to reflect the new 464xlat IP address.
             mDeps.configurePacketSocket(readSock6.getFileDescriptor(), v6Str, ifIndex);
-        } catch (IOException e) {
-            try {
-                untagSocket(cookie);
-            } catch (IOException e2) {
-                Log.e(TAG, "untagSocket cookie " + cookie + " failed: " + e2);
+
+            // [4] Open, configure and bring up the tun interface.
+            // Create the v4-... tun interface.
+            final String tunIface = CLAT_PREFIX + iface;
+            tunFd = mDeps.adoptFd(mDeps.createTunInterface(tunIface));
+            final int tunIfIndex = mDeps.getInterfaceIndex(tunIface);
+            if (tunIfIndex == INVALID_IFINDEX) {
+                throw new IOException("Fail to get interface index for interface " + tunIface);
             }
-            maybeCleanUp(tunFd, readSock6, writeSock6);
-            throw new IOException("configure packet socket failed: " + e);
-        }
+            // disable IPv6 on it - failing to do so is not a critical error
+            mNetd.interfaceSetEnableIPv6(tunIface, false /* enabled */);
+            // Detect ipv4 mtu.
+            final int detectedMtu = mDeps.detectMtu(pfx96Str,
+                    ByteBuffer.wrap(GOOGLE_DNS_4.getAddress()).getInt(), fwmark);
+            final int mtu = adjustMtu(detectedMtu);
+            Log.i(TAG, "detected ipv4 mtu of " + detectedMtu + " adjusted to " + mtu);
+            // Config tun interface mtu, address and bring up.
+            mNetd.interfaceSetMtu(tunIface, mtu);
+            final InterfaceConfigurationParcel ifConfig = new InterfaceConfigurationParcel();
+            ifConfig.ifName = tunIface;
+            ifConfig.ipv4Addr = v4Str;
+            ifConfig.prefixLength = 32;
+            ifConfig.hwAddr = "";
+            ifConfig.flags = new String[] {IF_STATE_UP};
+            mNetd.interfaceSetCfg(ifConfig);
 
-        // [5] Start clatd.
-        final int pid;
-        try {
-            pid = mDeps.startClatd(tunFd.getFileDescriptor(), readSock6.getFileDescriptor(),
-                    writeSock6.getFileDescriptor(), iface, pfx96Str, v4Str, v6Str);
-        } catch (IOException e) {
-            try {
-                untagSocket(cookie);
-            } catch (IOException e2) {
-                Log.e(TAG, "untagSocket cookie " + cookie + " failed: " + e2);
+            // [5] Start clatd.
+            final int pid = mDeps.startClatd(tunFd.getFileDescriptor(),
+                    readSock6.getFileDescriptor(), writeSock6.getFileDescriptor(), iface, pfx96Str,
+                    v4Str, v6Str);
+
+            // [6] Initialize and store clatd tracker object.
+            mClatdTracker = new ClatdTracker(iface, ifIndex, tunIface, tunIfIndex, v4, v6, pfx96,
+                    pid, cookie);
+
+            // [7] Start BPF
+            maybeStartBpf(mClatdTracker);
+
+            return v6Str;
+        } catch (IOException | RemoteException | ServiceSpecificException | ClassCastException
+                 | IllegalArgumentException | NullPointerException e) {
+            if (isSocketTagged) {
+                try {
+                    untagSocket(cookie);
+                } catch (IOException e2) {
+                    Log.e(TAG, "untagSocket cookie " + cookie + " failed: " + e2);
+                }
             }
-            throw new IOException("Error start clatd on " + iface + ": " + e);
-        } finally {
-            // The file descriptors have been duplicated (dup2) to clatd in native_startClatd().
-            // Close these file descriptor stubs which are unused anymore.
-            maybeCleanUp(tunFd, readSock6, writeSock6);
+            try {
+                if (tunFd != null) {
+                    tunFd.close();
+                }
+                if (readSock6 != null) {
+                    readSock6.close();
+                }
+                if (writeSock6 != null) {
+                    writeSock6.close();
+                }
+            } catch (IOException e2) {
+                Log.e(TAG, "Fail to cleanup fd ", e);
+            }
+            throw new IOException("Failed to start clat ", e);
         }
-
-        // [6] Initialize and store clatd tracker object.
-        mClatdTracker = new ClatdTracker(iface, ifIndex, tunIface, tunIfIndex, v4, v6, pfx96,
-                pid, cookie);
-
-        // [7] Start BPF
-        maybeStartBpf(mClatdTracker);
-
-        return v6Str;
     }
 
     private void maybeStopBpf(final ClatdTracker tracker) {
diff --git a/service/src/com/android/server/connectivity/DnsManager.java b/service/src/com/android/server/connectivity/DnsManager.java
index 8e6854a..ac02229 100644
--- a/service/src/com/android/server/connectivity/DnsManager.java
+++ b/service/src/com/android/server/connectivity/DnsManager.java
@@ -390,6 +390,7 @@
                 : new String[0];            // Off
         paramsParcel.transportTypes = nc.getTransportTypes();
         paramsParcel.meteredNetwork = nc.isMetered();
+        paramsParcel.interfaceNames = lp.getAllInterfaceNames().toArray(new String[0]);
         // Prepare to track the validation status of the DNS servers in the
         // resolver config when private DNS is in opportunistic or strict mode.
         if (useTls) {
@@ -403,13 +404,14 @@
         }
 
         Log.d(TAG, String.format("sendDnsConfigurationForNetwork(%d, %s, %s, %d, %d, %d, %d, "
-                + "%d, %d, %s, %s, %s, %b)", paramsParcel.netId,
+                + "%d, %d, %s, %s, %s, %b, %s)", paramsParcel.netId,
                 Arrays.toString(paramsParcel.servers), Arrays.toString(paramsParcel.domains),
                 paramsParcel.sampleValiditySeconds, paramsParcel.successThreshold,
                 paramsParcel.minSamples, paramsParcel.maxSamples, paramsParcel.baseTimeoutMsec,
                 paramsParcel.retryCount, paramsParcel.tlsName,
                 Arrays.toString(paramsParcel.tlsServers),
-                Arrays.toString(paramsParcel.transportTypes), paramsParcel.meteredNetwork));
+                Arrays.toString(paramsParcel.transportTypes), paramsParcel.meteredNetwork,
+                Arrays.toString(paramsParcel.interfaceNames)));
 
         try {
             mDnsResolver.setResolverConfiguration(paramsParcel);
diff --git a/service/src/com/android/server/connectivity/DscpPolicyTracker.java b/service/src/com/android/server/connectivity/DscpPolicyTracker.java
index 15d6adb..9c2b9e8 100644
--- a/service/src/com/android/server/connectivity/DscpPolicyTracker.java
+++ b/service/src/com/android/server/connectivity/DscpPolicyTracker.java
@@ -64,6 +64,8 @@
         return "/sys/fs/bpf/net_shared/map_" + which + "_map";
     }
 
+    private final boolean mHaveProgram = TcUtils.isBpfProgramUsable(PROG_PATH);
+
     private Set<String> mAttachedIfaces;
 
     private final BpfMap<Struct.S32, DscpPolicyValue> mBpfDscpIpv4Policies;
@@ -325,6 +327,7 @@
      * Attach BPF program
      */
     private boolean attachProgram(@NonNull String iface) {
+        if (!mHaveProgram) return false;
         try {
             NetworkInterface netIface = NetworkInterface.getByName(iface);
             TcUtils.tcFilterAddDevBpf(netIface.getIndex(), false, PRIO_DSCP, (short) ETH_P_ALL,
diff --git a/service/src/com/android/server/connectivity/Nat464Xlat.java b/service/src/com/android/server/connectivity/Nat464Xlat.java
index 065922d..489dab5 100644
--- a/service/src/com/android/server/connectivity/Nat464Xlat.java
+++ b/service/src/com/android/server/connectivity/Nat464Xlat.java
@@ -202,13 +202,13 @@
             try {
                 addrStr = mClatCoordinator.clatStart(baseIface, getNetId(), mNat64PrefixInUse);
             } catch (IOException e) {
-                Log.e(TAG, "Error starting clatd on " + baseIface + ": " + e);
+                Log.e(TAG, "Error starting clatd on " + baseIface, e);
             }
         } else {
             try {
                 addrStr = mNetd.clatdStart(baseIface, mNat64PrefixInUse.toString());
             } catch (RemoteException | ServiceSpecificException e) {
-                Log.e(TAG, "Error starting clatd on " + baseIface + ": " + e);
+                Log.e(TAG, "Error starting clatd on " + baseIface, e);
             }
         }
         mIface = CLAT_PREFIX + baseIface;
@@ -217,7 +217,7 @@
         try {
             mIPv6Address = (Inet6Address) InetAddresses.parseNumericAddress(addrStr);
         } catch (ClassCastException | IllegalArgumentException | NullPointerException e) {
-            Log.e(TAG, "Invalid IPv6 address " + addrStr);
+            Log.e(TAG, "Invalid IPv6 address " + addrStr , e);
         }
         if (mPrefixDiscoveryRunning && !isPrefixDiscoveryNeeded()) {
             stopPrefixDiscovery();
diff --git a/staticlibs/device/com/android/net/module/util/TcUtils.java b/staticlibs/device/com/android/net/module/util/TcUtils.java
index 9d2fb7f..a6b222f 100644
--- a/staticlibs/device/com/android/net/module/util/TcUtils.java
+++ b/staticlibs/device/com/android/net/module/util/TcUtils.java
@@ -101,4 +101,12 @@
      * @throws IOException
      */
     public static native void tcQdiscAddDevClsact(int ifIndex) throws IOException;
+
+    /**
+     * Attempt to fetch a bpf program from a path.
+     * Return true on success, false on non-existence or any other failure.
+     *
+     * @param bpfProgPath
+     */
+    public static native boolean isBpfProgramUsable(String bpfProgPath);
 }
diff --git a/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkLinkMessage.java b/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkLinkMessage.java
index 92ec0c4..df7010e 100644
--- a/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkLinkMessage.java
+++ b/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkLinkMessage.java
@@ -41,6 +41,10 @@
     public static final short IFLA_ADDRESS   = 1;
     public static final short IFLA_IFNAME    = 3;
     public static final short IFLA_MTU       = 4;
+    public static final short IFLA_INET6_ADDR_GEN_MODE = 8;
+    public static final short IFLA_AF_SPEC = 26;
+
+    public static final short IN6_ADDR_GEN_MODE_NONE = 1;
 
     private int mMtu;
     @NonNull
diff --git a/staticlibs/device/com/android/net/module/util/netlink/StructIfinfoMsg.java b/staticlibs/device/com/android/net/module/util/netlink/StructIfinfoMsg.java
index 02d1574..28eeaea 100644
--- a/staticlibs/device/com/android/net/module/util/netlink/StructIfinfoMsg.java
+++ b/staticlibs/device/com/android/net/module/util/netlink/StructIfinfoMsg.java
@@ -49,7 +49,7 @@
     @Field(order = 4, type = Type.U32)
     public final long change;
 
-    StructIfinfoMsg(short family, int type, int index, long flags, long change) {
+    public StructIfinfoMsg(short family, int type, int index, long flags, long change) {
         this.family = family;
         this.type = type;
         this.index = index;
diff --git a/staticlibs/framework/com/android/net/module/util/NetworkStackConstants.java b/staticlibs/framework/com/android/net/module/util/NetworkStackConstants.java
index 7c4abe0..19d8bbe 100644
--- a/staticlibs/framework/com/android/net/module/util/NetworkStackConstants.java
+++ b/staticlibs/framework/com/android/net/module/util/NetworkStackConstants.java
@@ -179,6 +179,7 @@
     public static final int ICMPV6_RA_HEADER_LEN = 16;
     public static final int ICMPV6_NS_HEADER_LEN = 24;
     public static final int ICMPV6_NA_HEADER_LEN = 24;
+    public static final int ICMPV6_ND_OPTION_TLLA_LEN = 8;
 
     public static final int NEIGHBOR_ADVERTISEMENT_FLAG_ROUTER    = 1 << 31;
     public static final int NEIGHBOR_ADVERTISEMENT_FLAG_SOLICITED = 1 << 30;
diff --git a/staticlibs/native/bpf_headers/include/bpf/KernelUtils.h b/staticlibs/native/bpf_headers/include/bpf/KernelUtils.h
index 59257b8..417a5c4 100644
--- a/staticlibs/native/bpf_headers/include/bpf/KernelUtils.h
+++ b/staticlibs/native/bpf_headers/include/bpf/KernelUtils.h
@@ -42,10 +42,26 @@
     return kver;
 }
 
-static inline __unused bool isAtLeastKernelVersion(unsigned major, unsigned minor, unsigned sub) {
+static inline bool isAtLeastKernelVersion(unsigned major, unsigned minor, unsigned sub) {
     return kernelVersion() >= KVER(major, minor, sub);
 }
 
+static inline bool isKernelVersion(unsigned major, unsigned minor) {
+    return isAtLeastKernelVersion(major, minor, 0) && !isAtLeastKernelVersion(major, minor + 1, 0);
+}
+
+static inline bool __unused isLtsKernel() {
+    return isKernelVersion(4,  4) ||  // minimum for Android R
+           isKernelVersion(4,  9) ||  // minimum for Android S & T
+           isKernelVersion(4, 14) ||  // minimum for Android U
+           isKernelVersion(4, 19) ||  // minimum for Android V
+           isKernelVersion(5,  4) ||  // first supported in Android R
+           isKernelVersion(5, 10) ||  // first supported in Android S
+           isKernelVersion(5, 15) ||  // first supported in Android T
+           isKernelVersion(6,  1) ||  // first supported in Android U
+           isKernelVersion(6,  6);    // first supported in Android V
+}
+
 // Figure out the bitness of userspace.
 // Trivial and known at compile time.
 static constexpr bool isUserspace32bit() {
diff --git a/staticlibs/native/bpf_syscall_wrappers/include/BpfSyscallWrappers.h b/staticlibs/native/bpf_syscall_wrappers/include/BpfSyscallWrappers.h
index 9995cb9..cb02de8 100644
--- a/staticlibs/native/bpf_syscall_wrappers/include/BpfSyscallWrappers.h
+++ b/staticlibs/native/bpf_syscall_wrappers/include/BpfSyscallWrappers.h
@@ -148,6 +148,13 @@
     return bpfFdGet(pathname, BPF_F_RDONLY);
 }
 
+inline bool usableProgram(const char* pathname) {
+    int fd = retrieveProgram(pathname);
+    bool ok = (fd >= 0);
+    if (ok) close(fd);
+    return ok;
+}
+
 inline int attachProgram(bpf_attach_type type, const BPF_FD_TYPE prog_fd,
                          const BPF_FD_TYPE cg_fd, uint32_t flags = 0) {
     return bpf(BPF_PROG_ATTACH, {
diff --git a/staticlibs/native/bpfmapjni/com_android_net_module_util_TcUtils.cpp b/staticlibs/native/bpfmapjni/com_android_net_module_util_TcUtils.cpp
index ab83da6..2a587b6 100644
--- a/staticlibs/native/bpfmapjni/com_android_net_module_util_TcUtils.cpp
+++ b/staticlibs/native/bpfmapjni/com_android_net_module_util_TcUtils.cpp
@@ -19,6 +19,9 @@
 #include <nativehelper/scoped_utf_chars.h>
 #include <tcutils/tcutils.h>
 
+#define BPF_FD_JUST_USE_INT
+#include "BpfSyscallWrappers.h"
+
 namespace android {
 
 static void throwIOException(JNIEnv *env, const char *msg, int error) {
@@ -96,6 +99,14 @@
   }
 }
 
+static jboolean com_android_net_module_util_TcUtils_isBpfProgramUsable(JNIEnv *env,
+                                                                       jclass clazz,
+                                                                       jstring bpfProgPath) {
+    ScopedUtfChars pathname(env, bpfProgPath);
+    return bpf::usableProgram(pathname.c_str());
+}
+
+
 /*
  * JNI registration.
  */
@@ -111,6 +122,8 @@
      (void *)com_android_net_module_util_TcUtils_tcFilterDelDev},
     {"tcQdiscAddDevClsact", "(I)V",
      (void *)com_android_net_module_util_TcUtils_tcQdiscAddDevClsact},
+    {"isBpfProgramUsable", "(Ljava/lang/String;)Z",
+     (void *)com_android_net_module_util_TcUtils_isBpfProgramUsable},
 };
 
 int register_com_android_net_module_util_TcUtils(JNIEnv *env,
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/SetFeatureFlagsRule.kt b/staticlibs/testutils/devicetests/com/android/testutils/SetFeatureFlagsRule.kt
index 4185b05..d5e91c2 100644
--- a/staticlibs/testutils/devicetests/com/android/testutils/SetFeatureFlagsRule.kt
+++ b/staticlibs/testutils/devicetests/com/android/testutils/SetFeatureFlagsRule.kt
@@ -24,6 +24,7 @@
  * A JUnit Rule that sets feature flags based on `@FeatureFlag` annotations.
  *
  * This rule enables dynamic control of feature flag states during testing.
+ * And restores the original values after performing tests.
  *
  * **Usage:**
  * ```kotlin
@@ -31,6 +32,8 @@
  *   @get:Rule
  *   val setFeatureFlagsRule = SetFeatureFlagsRule(setFlagsMethod = (name, enabled) -> {
  *     // Custom handling code.
+ *   }, (name) -> {
+ *     // Custom getter code to retrieve the original values.
  *   })
  *
  *   // ... test methods with @FeatureFlag annotations
@@ -41,7 +44,10 @@
  * }
  * ```
  */
-class SetFeatureFlagsRule(val setFlagsMethod: (name: String, enabled: Boolean) -> Unit) : TestRule {
+class SetFeatureFlagsRule(
+    val setFlagsMethod: (name: String, enabled: Boolean?) -> Unit,
+                          val getFlagsMethod: (name: String) -> Boolean?
+) : TestRule {
     /**
      * This annotation marks a test method as requiring a specific feature flag to be configured.
      *
@@ -69,13 +75,20 @@
                     FeatureFlag::class.java
                 )
 
+                val valuesToBeRestored = mutableMapOf<String, Boolean?>()
                 for (featureFlagAnnotation in featureFlagAnnotations) {
+                    valuesToBeRestored[featureFlagAnnotation.name] =
+                            getFlagsMethod(featureFlagAnnotation.name)
                     setFlagsMethod(featureFlagAnnotation.name, featureFlagAnnotation.enabled)
                 }
 
                 // Execute the test method, which includes methods annotated with
                 // @Before, @Test and @After.
                 base.evaluate()
+
+                valuesToBeRestored.forEach {
+                    setFlagsMethod(it.key, it.value)
+                }
             }
         }
     }
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkCallback.kt b/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkCallback.kt
index 05c0444..f76916a 100644
--- a/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkCallback.kt
+++ b/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkCallback.kt
@@ -91,6 +91,7 @@
             override val network: Network,
             val reason: Int
         ) : CallbackEntry()
+
         // Convenience constants for expecting a type
         companion object {
             @JvmField
@@ -216,7 +217,11 @@
     ) : this(null, timeoutMs, noCallbackTimeoutMs, waiterFunc)
 
     fun createLinkedCopy() = TestableNetworkCallback(
-            this, defaultTimeoutMs, defaultNoCallbackTimeoutMs, waiterFunc)
+        this,
+        defaultTimeoutMs,
+        defaultNoCallbackTimeoutMs,
+        waiterFunc
+    )
 
     // The last available network, or null if any network was lost since the last call to
     // onAvailable. TODO : fix this by fixing the tests that rely on this behavior
@@ -402,8 +407,11 @@
         from: Int = mark,
         crossinline predicate: (T) -> Boolean = { true }
     ): T = history.poll(timeoutMs, from) { it is T && predicate(it) }.also {
-        assertNotNull(it, "Callback ${T::class} not received within ${timeoutMs}ms. " +
-                "Got ${history.backtrace()}")
+        assertNotNull(
+            it,
+            "Callback ${T::class} not received within ${timeoutMs}ms. " +
+                "Got ${history.backtrace()}"
+        )
     } as T
 
     @JvmOverloads
@@ -412,8 +420,11 @@
         timeoutMs: Long = defaultTimeoutMs,
         predicate: (cb: T) -> Boolean = { true }
     ) = history.poll(timeoutMs) { type.java.isInstance(it) && predicate(it as T) }.also {
-        assertNotNull(it, "Callback ${type.java} not received within ${timeoutMs}ms. " +
-                "Got ${history.backtrace()}")
+        assertNotNull(
+            it,
+            "Callback ${type.java} not received within ${timeoutMs}ms. " +
+                "Got ${history.backtrace()}"
+        )
     } as T
 
     fun <T : CallbackEntry> eventuallyExpect(
@@ -422,8 +433,11 @@
         from: Int = mark,
         predicate: (cb: T) -> Boolean = { true }
     ) = history.poll(timeoutMs, from) { type.java.isInstance(it) && predicate(it as T) }.also {
-        assertNotNull(it, "Callback ${type.java} not received within ${timeoutMs}ms. " +
-                "Got ${history.backtrace()}")
+        assertNotNull(
+            it,
+            "Callback ${type.java} not received within ${timeoutMs}ms. " +
+                "Got ${history.backtrace()}"
+        )
     } as T
 
     // Expects onAvailable and the callbacks that follow it. These are:
@@ -534,8 +548,13 @@
         blockedReason: Int,
         tmt: Long = defaultTimeoutMs
     ) {
-        expectAvailableCallbacks(net, validated = false, suspended = false,
-                blockedReason = blockedReason, tmt = tmt)
+        expectAvailableCallbacks(
+            net,
+            validated = false,
+            suspended = false,
+            blockedReason = blockedReason,
+            tmt = tmt
+        )
         expectCaps(net, tmt) { it.hasCapability(NET_CAPABILITY_VALIDATED) }
     }
 
diff --git a/tests/cts/hostside-network-policy/Android.bp b/tests/cts/hostside-network-policy/Android.bp
new file mode 100644
index 0000000..c3ce0b9
--- /dev/null
+++ b/tests/cts/hostside-network-policy/Android.bp
@@ -0,0 +1,46 @@
+// Copyright (C) 2024 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_team: "trendy_team_framework_backstage_power",
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+java_test_host {
+    name: "CtsHostsideNetworkPolicyTests",
+    defaults: ["cts_defaults"],
+    // Only compile source java files in this apk.
+    srcs: [
+        "src/**/*.java",
+        ":ArgumentConstants",
+    ],
+    libs: [
+        "cts-tradefed",
+        "tradefed",
+    ],
+    static_libs: [
+        "modules-utils-build-testing",
+    ],
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+        "sts",
+    ],
+    data: [
+        ":CtsHostsideNetworkPolicyTestsApp",
+        ":CtsHostsideNetworkPolicyTestsApp2",
+    ],
+    per_testcase_directory: true,
+}
diff --git a/tests/cts/hostside-network-policy/AndroidTest.xml b/tests/cts/hostside-network-policy/AndroidTest.xml
new file mode 100644
index 0000000..44f77f8
--- /dev/null
+++ b/tests/cts/hostside-network-policy/AndroidTest.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 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.
+-->
+<configuration description="Config for CTS network policy host test cases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="networking" />
+    <option name="config-descriptor:metadata" key="token" value="SIM_CARD" />
+    <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.LocationCheck" />
+    <target_preparer class="com.android.cts.netpolicy.NetworkPolicyTestsPreparer" />
+
+    <!-- Enabling change id ALLOW_TEST_API_ACCESS allows that package to access @TestApi methods -->
+    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+        <option name="run-command" value="am compat enable ALLOW_TEST_API_ACCESS com.android.cts.netpolicy.hostside.app2" />
+        <option name="teardown-command" value="am compat reset ALLOW_TEST_API_ACCESS com.android.cts.netpolicy.hostside.app2" />
+        <option name="teardown-command" value="cmd power set-mode 0" />
+        <option name="teardown-command" value="cmd battery reset" />
+        <option name="teardown-command" value="cmd netpolicy stop-watching" />
+    </target_preparer>
+
+    <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+        <option name="force-skip-system-props" value="true" />
+        <option name="set-global-setting" key="verifier_verify_adb_installs" value="0" />
+        <option name="set-global-setting" key="low_power_standby_enabled" value="0" />
+    </target_preparer>
+
+    <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
+        <option name="jar" value="CtsHostsideNetworkPolicyTests.jar" />
+        <option name="runtime-hint" value="3m56s" />
+    </test>
+
+    <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+        <option name="directory-keys" value="/sdcard/CtsHostsideNetworkPolicyTests" />
+        <option name="collect-on-run-ended-only" value="true" />
+    </metrics_collector>
+</configuration>
diff --git a/tests/cts/hostside-network-policy/OWNERS b/tests/cts/hostside-network-policy/OWNERS
new file mode 100644
index 0000000..ea83e61
--- /dev/null
+++ b/tests/cts/hostside-network-policy/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 61373
+# Inherits parent owners
+include platform/frameworks/base:/services/core/java/com/android/server/net/OWNERS
diff --git a/tests/cts/hostside-network-policy/TEST_MAPPING b/tests/cts/hostside-network-policy/TEST_MAPPING
new file mode 100644
index 0000000..57ac4f7
--- /dev/null
+++ b/tests/cts/hostside-network-policy/TEST_MAPPING
@@ -0,0 +1,27 @@
+{
+  "presubmit-large": [
+    {
+      "name": "CtsHostsideNetworkPolicyTests",
+      "options": [
+        {
+          "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+        },
+        {
+          "exclude-annotation": "android.platform.test.annotations.RequiresDevice"
+        }
+      ]
+    }
+  ],
+  "postsubmit": [
+    {
+      // Postsubmit on virtual devices to monitor flakiness of all tests that don't require a
+      // physical device
+      "name": "CtsHostsideNetworkPolicyTests",
+      "options": [
+        {
+          "exclude-annotation": "androidx.test.filters.RequiresDevice"
+        }
+      ]
+    }
+  ]
+}
diff --git a/tests/cts/hostside-network-policy/aidl/Android.bp b/tests/cts/hostside-network-policy/aidl/Android.bp
new file mode 100644
index 0000000..b182090
--- /dev/null
+++ b/tests/cts/hostside-network-policy/aidl/Android.bp
@@ -0,0 +1,26 @@
+// Copyright (C) 2024 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_team: "trendy_team_framework_backstage_power",
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+java_test_helper_library {
+    name: "CtsHostsideNetworkPolicyTestsAidl",
+    sdk_version: "current",
+    srcs: [
+        "com/android/cts/netpolicy/hostside/*.aidl",
+    ],
+}
diff --git a/tests/cts/hostside/aidl/com/android/cts/net/hostside/IMyService.aidl b/tests/cts/hostside-network-policy/aidl/com/android/cts/netpolicy/hostside/IMyService.aidl
similarity index 86%
rename from tests/cts/hostside/aidl/com/android/cts/net/hostside/IMyService.aidl
rename to tests/cts/hostside-network-policy/aidl/com/android/cts/netpolicy/hostside/IMyService.aidl
index 906024b..068d9d8 100644
--- a/tests/cts/hostside/aidl/com/android/cts/net/hostside/IMyService.aidl
+++ b/tests/cts/hostside-network-policy/aidl/com/android/cts/netpolicy/hostside/IMyService.aidl
@@ -14,12 +14,12 @@
  * limitations under the License.
  */
 
-package com.android.cts.net.hostside;
+package com.android.cts.netpolicy.hostside;
 
 import android.app.job.JobInfo;
 
-import com.android.cts.net.hostside.INetworkCallback;
-import com.android.cts.net.hostside.NetworkCheckResult;
+import com.android.cts.netpolicy.hostside.INetworkCallback;
+import com.android.cts.netpolicy.hostside.NetworkCheckResult;
 
 interface IMyService {
     void registerBroadcastReceiver();
diff --git a/tests/cts/hostside/aidl/com/android/cts/net/hostside/INetworkCallback.aidl b/tests/cts/hostside-network-policy/aidl/com/android/cts/netpolicy/hostside/INetworkCallback.aidl
similarity index 95%
rename from tests/cts/hostside/aidl/com/android/cts/net/hostside/INetworkCallback.aidl
rename to tests/cts/hostside-network-policy/aidl/com/android/cts/netpolicy/hostside/INetworkCallback.aidl
index 2048bab..38efc7b 100644
--- a/tests/cts/hostside/aidl/com/android/cts/net/hostside/INetworkCallback.aidl
+++ b/tests/cts/hostside-network-policy/aidl/com/android/cts/netpolicy/hostside/INetworkCallback.aidl
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.cts.net.hostside;
+package com.android.cts.netpolicy.hostside;
 
 import android.net.Network;
 import android.net.NetworkCapabilities;
diff --git a/tests/cts/hostside/aidl/com/android/cts/net/hostside/INetworkStateObserver.aidl b/tests/cts/hostside-network-policy/aidl/com/android/cts/netpolicy/hostside/INetworkStateObserver.aidl
similarity index 89%
rename from tests/cts/hostside/aidl/com/android/cts/net/hostside/INetworkStateObserver.aidl
rename to tests/cts/hostside-network-policy/aidl/com/android/cts/netpolicy/hostside/INetworkStateObserver.aidl
index 8ef4659..c6b7a1c 100644
--- a/tests/cts/hostside/aidl/com/android/cts/net/hostside/INetworkStateObserver.aidl
+++ b/tests/cts/hostside-network-policy/aidl/com/android/cts/netpolicy/hostside/INetworkStateObserver.aidl
@@ -14,11 +14,11 @@
  * limitations under the License.
  */
 
-package com.android.cts.net.hostside;
+package com.android.cts.netpolicy.hostside;
 
 import android.net.NetworkInfo;
 
-import com.android.cts.net.hostside.NetworkCheckResult;
+import com.android.cts.netpolicy.hostside.NetworkCheckResult;
 
 interface INetworkStateObserver {
     void onNetworkStateChecked(int resultCode, in NetworkCheckResult networkCheckResult);
diff --git a/tests/cts/hostside/aidl/com/android/cts/net/hostside/NetworkCheckResult.aidl b/tests/cts/hostside-network-policy/aidl/com/android/cts/netpolicy/hostside/NetworkCheckResult.aidl
similarity index 94%
rename from tests/cts/hostside/aidl/com/android/cts/net/hostside/NetworkCheckResult.aidl
rename to tests/cts/hostside-network-policy/aidl/com/android/cts/netpolicy/hostside/NetworkCheckResult.aidl
index cdd6b70..7aac2ab 100644
--- a/tests/cts/hostside/aidl/com/android/cts/net/hostside/NetworkCheckResult.aidl
+++ b/tests/cts/hostside-network-policy/aidl/com/android/cts/netpolicy/hostside/NetworkCheckResult.aidl
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.cts.net.hostside;
+package com.android.cts.netpolicy.hostside;
 
 import android.net.NetworkInfo;
 
diff --git a/tests/cts/hostside-network-policy/app/Android.bp b/tests/cts/hostside-network-policy/app/Android.bp
new file mode 100644
index 0000000..a31c843
--- /dev/null
+++ b/tests/cts/hostside-network-policy/app/Android.bp
@@ -0,0 +1,57 @@
+//
+// Copyright (C) 2024 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_team: "trendy_team_framework_backstage_power",
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+java_defaults {
+    name: "CtsHostsideNetworkPolicyTestsAppDefaults",
+    platform_apis: true,
+    static_libs: [
+        "CtsHostsideNetworkPolicyTestsAidl",
+        "androidx.test.ext.junit",
+        "androidx.test.rules",
+        "androidx.test.uiautomator_uiautomator",
+        "compatibility-device-util-axt",
+        "cts-net-utils",
+        "ctstestrunner-axt",
+        "modules-utils-build",
+    ],
+    libs: [
+        "android.test.runner",
+        "android.test.base",
+    ],
+    srcs: [
+        "src/**/*.java",
+        ":ArgumentConstants",
+    ],
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "general-tests",
+        "sts",
+    ],
+}
+
+android_test_helper_app {
+    name: "CtsHostsideNetworkPolicyTestsApp",
+    defaults: [
+        "cts_support_defaults",
+        "framework-connectivity-test-defaults",
+        "CtsHostsideNetworkPolicyTestsAppDefaults",
+    ],
+}
diff --git a/tests/cts/hostside-network-policy/app/AndroidManifest.xml b/tests/cts/hostside-network-policy/app/AndroidManifest.xml
new file mode 100644
index 0000000..f19e35f
--- /dev/null
+++ b/tests/cts/hostside-network-policy/app/AndroidManifest.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 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.netpolicy.hostside">
+
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
+    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
+    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
+    <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
+    <uses-permission android:name="android.permission.INTERNET"/>
+    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
+    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
+    <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
+    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
+    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/>
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.WAKE_LOCK" />
+
+    <application android:requestLegacyExternalStorage="true">
+        <uses-library android:name="android.test.runner"/>
+        <service android:name=".MyNotificationListenerService"
+             android:label="MyNotificationListenerService"
+             android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.service.notification.NotificationListenerService"/>
+            </intent-filter>
+        </service>
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="com.android.cts.netpolicy.hostside"/>
+
+</manifest>
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractAppIdleTestCase.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AbstractAppIdleTestCase.java
similarity index 96%
rename from tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractAppIdleTestCase.java
rename to tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AbstractAppIdleTestCase.java
index d9ff539..19e4364 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractAppIdleTestCase.java
+++ b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AbstractAppIdleTestCase.java
@@ -14,10 +14,10 @@
  * limitations under the License.
  */
 
-package com.android.cts.net.hostside;
+package com.android.cts.netpolicy.hostside;
 
-import static com.android.cts.net.hostside.Property.APP_STANDBY_MODE;
-import static com.android.cts.net.hostside.Property.BATTERY_SAVER_MODE;
+import static com.android.cts.netpolicy.hostside.Property.APP_STANDBY_MODE;
+import static com.android.cts.netpolicy.hostside.Property.BATTERY_SAVER_MODE;
 
 import static org.junit.Assert.assertEquals;
 
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractBatterySaverModeTestCase.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AbstractBatterySaverModeTestCase.java
similarity index 96%
rename from tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractBatterySaverModeTestCase.java
rename to tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AbstractBatterySaverModeTestCase.java
index 0d7365f..ae226e2 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractBatterySaverModeTestCase.java
+++ b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AbstractBatterySaverModeTestCase.java
@@ -14,9 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.cts.net.hostside;
+package com.android.cts.netpolicy.hostside;
 
-import static com.android.cts.net.hostside.Property.BATTERY_SAVER_MODE;
+import static com.android.cts.netpolicy.hostside.Property.BATTERY_SAVER_MODE;
 
 import org.junit.After;
 import org.junit.Before;
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractDefaultRestrictionsTest.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AbstractDefaultRestrictionsTest.java
similarity index 98%
rename from tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractDefaultRestrictionsTest.java
rename to tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AbstractDefaultRestrictionsTest.java
index 8a3e790..da633c0 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractDefaultRestrictionsTest.java
+++ b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AbstractDefaultRestrictionsTest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.cts.net.hostside;
+package com.android.cts.netpolicy.hostside;
 
 import static android.app.ActivityManager.PROCESS_STATE_TOP_SLEEPING;
 
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractDozeModeTestCase.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AbstractDozeModeTestCase.java
similarity index 95%
rename from tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractDozeModeTestCase.java
rename to tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AbstractDozeModeTestCase.java
index b037953..0c8cb70 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractDozeModeTestCase.java
+++ b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AbstractDozeModeTestCase.java
@@ -14,12 +14,12 @@
  * limitations under the License.
  */
 
-package com.android.cts.net.hostside;
+package com.android.cts.netpolicy.hostside;
 
 import static android.app.ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
 
-import static com.android.cts.net.hostside.Property.DOZE_MODE;
-import static com.android.cts.net.hostside.Property.NOT_LOW_RAM_DEVICE;
+import static com.android.cts.netpolicy.hostside.Property.DOZE_MODE;
+import static com.android.cts.netpolicy.hostside.Property.NOT_LOW_RAM_DEVICE;
 
 import android.os.SystemClock;
 
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractExpeditedJobTest.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AbstractExpeditedJobTest.java
similarity index 88%
rename from tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractExpeditedJobTest.java
rename to tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AbstractExpeditedJobTest.java
index 7cac2af..5435920 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractExpeditedJobTest.java
+++ b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AbstractExpeditedJobTest.java
@@ -14,14 +14,14 @@
  * limitations under the License.
  */
 
-package com.android.cts.net.hostside;
+package com.android.cts.netpolicy.hostside;
 
-import static com.android.cts.net.hostside.NetworkPolicyTestUtils.setRestrictBackground;
-import static com.android.cts.net.hostside.Property.APP_STANDBY_MODE;
-import static com.android.cts.net.hostside.Property.BATTERY_SAVER_MODE;
-import static com.android.cts.net.hostside.Property.DATA_SAVER_MODE;
-import static com.android.cts.net.hostside.Property.DOZE_MODE;
-import static com.android.cts.net.hostside.Property.METERED_NETWORK;
+import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.setRestrictBackground;
+import static com.android.cts.netpolicy.hostside.Property.APP_STANDBY_MODE;
+import static com.android.cts.netpolicy.hostside.Property.BATTERY_SAVER_MODE;
+import static com.android.cts.netpolicy.hostside.Property.DATA_SAVER_MODE;
+import static com.android.cts.netpolicy.hostside.Property.DOZE_MODE;
+import static com.android.cts.netpolicy.hostside.Property.METERED_NETWORK;
 
 import org.junit.After;
 import org.junit.Before;
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AbstractRestrictBackgroundNetworkTestCase.java
similarity index 96%
rename from tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
rename to tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AbstractRestrictBackgroundNetworkTestCase.java
index 4437986..d0203c5 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
+++ b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AbstractRestrictBackgroundNetworkTestCase.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.cts.net.hostside;
+package com.android.cts.netpolicy.hostside;
 
 import static android.app.ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
 import static android.app.ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
@@ -23,18 +23,18 @@
 import static android.net.ConnectivityManager.ACTION_RESTRICT_BACKGROUND_CHANGED;
 import static android.os.BatteryManager.BATTERY_PLUGGED_ANY;
 
-import static com.android.cts.net.arguments.InstrumentationArguments.ARG_CONNECTION_CHECK_CUSTOM_URL;
-import static com.android.cts.net.arguments.InstrumentationArguments.ARG_WAIVE_BIND_PRIORITY;
-import static com.android.cts.net.hostside.NetworkPolicyTestUtils.executeShellCommand;
-import static com.android.cts.net.hostside.NetworkPolicyTestUtils.forceRunJob;
-import static com.android.cts.net.hostside.NetworkPolicyTestUtils.getConnectivityManager;
-import static com.android.cts.net.hostside.NetworkPolicyTestUtils.getContext;
-import static com.android.cts.net.hostside.NetworkPolicyTestUtils.getInstrumentation;
-import static com.android.cts.net.hostside.NetworkPolicyTestUtils.isAppStandbySupported;
-import static com.android.cts.net.hostside.NetworkPolicyTestUtils.isBatterySaverSupported;
-import static com.android.cts.net.hostside.NetworkPolicyTestUtils.isDozeModeSupported;
-import static com.android.cts.net.hostside.NetworkPolicyTestUtils.restrictBackgroundValueToString;
-import static com.android.cts.net.hostside.NetworkPolicyTestUtils.setRestrictBackgroundInternal;
+import static com.android.cts.netpolicy.arguments.InstrumentationArguments.ARG_CONNECTION_CHECK_CUSTOM_URL;
+import static com.android.cts.netpolicy.arguments.InstrumentationArguments.ARG_WAIVE_BIND_PRIORITY;
+import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.executeShellCommand;
+import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.forceRunJob;
+import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.getConnectivityManager;
+import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.getContext;
+import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.getInstrumentation;
+import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.isAppStandbySupported;
+import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.isBatterySaverSupported;
+import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.isDozeModeSupported;
+import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.restrictBackgroundValueToString;
+import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.setRestrictBackgroundInternal;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -93,8 +93,8 @@
 public abstract class AbstractRestrictBackgroundNetworkTestCase {
     public static final String TAG = "RestrictBackgroundNetworkTests";
 
-    protected static final String TEST_PKG = "com.android.cts.net.hostside";
-    protected static final String TEST_APP2_PKG = "com.android.cts.net.hostside.app2";
+    protected static final String TEST_PKG = "com.android.cts.netpolicy.hostside";
+    protected static final String TEST_APP2_PKG = "com.android.cts.netpolicy.hostside.app2";
     // TODO(b/321797685): Configure it via device-config once it is available.
     protected static final long PROCESS_STATE_TRANSITION_DELAY_MS = TimeUnit.SECONDS.toMillis(5);
 
@@ -112,17 +112,17 @@
     private static final String MANIFEST_RECEIVER = "ManifestReceiver";
     private static final String DYNAMIC_RECEIVER = "DynamicReceiver";
     private static final String ACTION_FINISH_ACTIVITY =
-            "com.android.cts.net.hostside.app2.action.FINISH_ACTIVITY";
+            "com.android.cts.netpolicy.hostside.app2.action.FINISH_ACTIVITY";
     private static final String ACTION_FINISH_JOB =
-            "com.android.cts.net.hostside.app2.action.FINISH_JOB";
+            "com.android.cts.netpolicy.hostside.app2.action.FINISH_JOB";
     // Copied from com.android.server.net.NetworkPolicyManagerService class
     private static final String ACTION_SNOOZE_WARNING =
             "com.android.server.net.action.SNOOZE_WARNING";
 
     private static final String ACTION_RECEIVER_READY =
-            "com.android.cts.net.hostside.app2.action.RECEIVER_READY";
+            "com.android.cts.netpolicy.hostside.app2.action.RECEIVER_READY";
     static final String ACTION_SHOW_TOAST =
-            "com.android.cts.net.hostside.app2.action.SHOW_TOAST";
+            "com.android.cts.netpolicy.hostside.app2.action.SHOW_TOAST";
 
     protected static final String NOTIFICATION_TYPE_CONTENT = "CONTENT";
     protected static final String NOTIFICATION_TYPE_DELETE = "DELETE";
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AppIdleMeteredTest.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AppIdleMeteredTest.java
similarity index 85%
rename from tests/cts/hostside/app/src/com/android/cts/net/hostside/AppIdleMeteredTest.java
rename to tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AppIdleMeteredTest.java
index f1858d6..6b802f6 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AppIdleMeteredTest.java
+++ b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AppIdleMeteredTest.java
@@ -14,9 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.cts.net.hostside;
+package com.android.cts.netpolicy.hostside;
 
-import static com.android.cts.net.hostside.Property.METERED_NETWORK;
+import static com.android.cts.netpolicy.hostside.Property.METERED_NETWORK;
 
 @RequiredProperties({METERED_NETWORK})
 public class AppIdleMeteredTest extends AbstractAppIdleTestCase {
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AppIdleNonMeteredTest.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AppIdleNonMeteredTest.java
similarity index 85%
rename from tests/cts/hostside/app/src/com/android/cts/net/hostside/AppIdleNonMeteredTest.java
rename to tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AppIdleNonMeteredTest.java
index e737a6d..2e725ae 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AppIdleNonMeteredTest.java
+++ b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AppIdleNonMeteredTest.java
@@ -14,9 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.cts.net.hostside;
+package com.android.cts.netpolicy.hostside;
 
-import static com.android.cts.net.hostside.Property.NON_METERED_NETWORK;
+import static com.android.cts.netpolicy.hostside.Property.NON_METERED_NETWORK;
 
 @RequiredProperties({NON_METERED_NETWORK})
 public class AppIdleNonMeteredTest extends AbstractAppIdleTestCase {
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/BatterySaverModeMeteredTest.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/BatterySaverModeMeteredTest.java
similarity index 85%
rename from tests/cts/hostside/app/src/com/android/cts/net/hostside/BatterySaverModeMeteredTest.java
rename to tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/BatterySaverModeMeteredTest.java
index c78ca2e..2e421f6 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/BatterySaverModeMeteredTest.java
+++ b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/BatterySaverModeMeteredTest.java
@@ -14,9 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.cts.net.hostside;
+package com.android.cts.netpolicy.hostside;
 
-import static com.android.cts.net.hostside.Property.METERED_NETWORK;
+import static com.android.cts.netpolicy.hostside.Property.METERED_NETWORK;
 
 @RequiredProperties({METERED_NETWORK})
 public class BatterySaverModeMeteredTest extends AbstractBatterySaverModeTestCase {
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/BatterySaverModeNonMeteredTest.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/BatterySaverModeNonMeteredTest.java
similarity index 85%
rename from tests/cts/hostside/app/src/com/android/cts/net/hostside/BatterySaverModeNonMeteredTest.java
rename to tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/BatterySaverModeNonMeteredTest.java
index fb52a54..0be5644 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/BatterySaverModeNonMeteredTest.java
+++ b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/BatterySaverModeNonMeteredTest.java
@@ -14,10 +14,10 @@
  * limitations under the License.
  */
 
-package com.android.cts.net.hostside;
+package com.android.cts.netpolicy.hostside;
 
 
-import static com.android.cts.net.hostside.Property.NON_METERED_NETWORK;
+import static com.android.cts.netpolicy.hostside.Property.NON_METERED_NETWORK;
 
 @RequiredProperties({NON_METERED_NETWORK})
 public class BatterySaverModeNonMeteredTest extends AbstractBatterySaverModeTestCase {
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/ConnOnActivityStartTest.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/ConnOnActivityStartTest.java
similarity index 85%
rename from tests/cts/hostside/app/src/com/android/cts/net/hostside/ConnOnActivityStartTest.java
rename to tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/ConnOnActivityStartTest.java
index 3e22a23..811190f 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/ConnOnActivityStartTest.java
+++ b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/ConnOnActivityStartTest.java
@@ -14,20 +14,20 @@
  * limitations under the License.
  */
 
-package com.android.cts.net.hostside;
+package com.android.cts.netpolicy.hostside;
 
 
 import static android.app.ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
 import static android.app.ActivityManager.PROCESS_STATE_TOP_SLEEPING;
 
-import static com.android.cts.net.hostside.NetworkPolicyTestUtils.getUiDevice;
-import static com.android.cts.net.hostside.NetworkPolicyTestUtils.setRestrictBackground;
-import static com.android.cts.net.hostside.Property.APP_STANDBY_MODE;
-import static com.android.cts.net.hostside.Property.BATTERY_SAVER_MODE;
-import static com.android.cts.net.hostside.Property.DATA_SAVER_MODE;
-import static com.android.cts.net.hostside.Property.DOZE_MODE;
-import static com.android.cts.net.hostside.Property.METERED_NETWORK;
-import static com.android.cts.net.hostside.Property.NON_METERED_NETWORK;
+import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.getUiDevice;
+import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.setRestrictBackground;
+import static com.android.cts.netpolicy.hostside.Property.APP_STANDBY_MODE;
+import static com.android.cts.netpolicy.hostside.Property.BATTERY_SAVER_MODE;
+import static com.android.cts.netpolicy.hostside.Property.DATA_SAVER_MODE;
+import static com.android.cts.netpolicy.hostside.Property.DOZE_MODE;
+import static com.android.cts.netpolicy.hostside.Property.METERED_NETWORK;
+import static com.android.cts.netpolicy.hostside.Property.NON_METERED_NETWORK;
 
 import static org.junit.Assume.assumeTrue;
 
@@ -53,6 +53,7 @@
     @After
     public final void tearDown() throws Exception {
         super.tearDown();
+        finishActivity();
         resetDeviceState();
     }
 
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/DataSaverModeTest.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/DataSaverModeTest.java
similarity index 95%
rename from tests/cts/hostside/app/src/com/android/cts/net/hostside/DataSaverModeTest.java
rename to tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/DataSaverModeTest.java
index 790e031..66e0d00 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/DataSaverModeTest.java
+++ b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/DataSaverModeTest.java
@@ -14,17 +14,17 @@
  * limitations under the License.
  */
 
-package com.android.cts.net.hostside;
+package com.android.cts.netpolicy.hostside;
 
 import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_DISABLED;
 import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_ENABLED;
 import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_WHITELISTED;
 
 import static com.android.compatibility.common.util.FeatureUtil.isTV;
-import static com.android.cts.net.hostside.NetworkPolicyTestUtils.setRestrictBackground;
-import static com.android.cts.net.hostside.Property.DATA_SAVER_MODE;
-import static com.android.cts.net.hostside.Property.METERED_NETWORK;
-import static com.android.cts.net.hostside.Property.NO_DATA_SAVER_MODE;
+import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.setRestrictBackground;
+import static com.android.cts.netpolicy.hostside.Property.DATA_SAVER_MODE;
+import static com.android.cts.netpolicy.hostside.Property.METERED_NETWORK;
+import static com.android.cts.netpolicy.hostside.Property.NO_DATA_SAVER_MODE;
 
 import static org.junit.Assert.fail;
 
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/DataWarningReceiverTest.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/DataWarningReceiverTest.java
similarity index 96%
rename from tests/cts/hostside/app/src/com/android/cts/net/hostside/DataWarningReceiverTest.java
rename to tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/DataWarningReceiverTest.java
index 13bbab6..69ca206 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/DataWarningReceiverTest.java
+++ b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/DataWarningReceiverTest.java
@@ -14,9 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.cts.net.hostside;
+package com.android.cts.netpolicy.hostside;
 
-import static com.android.cts.net.hostside.NetworkPolicyTestUtils.clearSnoozeTimestamps;
+import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.clearSnoozeTimestamps;
 
 import android.content.pm.PackageManager;
 import android.telephony.SubscriptionManager;
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/DefaultRestrictionsMeteredTest.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/DefaultRestrictionsMeteredTest.java
similarity index 86%
rename from tests/cts/hostside/app/src/com/android/cts/net/hostside/DefaultRestrictionsMeteredTest.java
rename to tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/DefaultRestrictionsMeteredTest.java
index f3a1026..810fd19 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/DefaultRestrictionsMeteredTest.java
+++ b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/DefaultRestrictionsMeteredTest.java
@@ -14,9 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.cts.net.hostside;
+package com.android.cts.netpolicy.hostside;
 
-import static com.android.cts.net.hostside.Property.METERED_NETWORK;
+import static com.android.cts.netpolicy.hostside.Property.METERED_NETWORK;
 
 @RequiredProperties({METERED_NETWORK})
 public class DefaultRestrictionsMeteredTest extends AbstractDefaultRestrictionsTest {
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/DefaultRestrictionsNonMeteredTest.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/DefaultRestrictionsNonMeteredTest.java
similarity index 85%
rename from tests/cts/hostside/app/src/com/android/cts/net/hostside/DefaultRestrictionsNonMeteredTest.java
rename to tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/DefaultRestrictionsNonMeteredTest.java
index 5651dd0..fef546c 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/DefaultRestrictionsNonMeteredTest.java
+++ b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/DefaultRestrictionsNonMeteredTest.java
@@ -14,9 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.cts.net.hostside;
+package com.android.cts.netpolicy.hostside;
 
-import static com.android.cts.net.hostside.Property.NON_METERED_NETWORK;
+import static com.android.cts.netpolicy.hostside.Property.NON_METERED_NETWORK;
 
 @RequiredProperties({NON_METERED_NETWORK})
 public class DefaultRestrictionsNonMeteredTest extends AbstractDefaultRestrictionsTest {
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/DozeModeMeteredTest.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/DozeModeMeteredTest.java
similarity index 85%
rename from tests/cts/hostside/app/src/com/android/cts/net/hostside/DozeModeMeteredTest.java
rename to tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/DozeModeMeteredTest.java
index 4306c99..741dd7e 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/DozeModeMeteredTest.java
+++ b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/DozeModeMeteredTest.java
@@ -14,9 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.cts.net.hostside;
+package com.android.cts.netpolicy.hostside;
 
-import static com.android.cts.net.hostside.Property.METERED_NETWORK;
+import static com.android.cts.netpolicy.hostside.Property.METERED_NETWORK;
 
 @RequiredProperties({METERED_NETWORK})
 public class DozeModeMeteredTest extends AbstractDozeModeTestCase {
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/DozeModeNonMeteredTest.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/DozeModeNonMeteredTest.java
similarity index 85%
rename from tests/cts/hostside/app/src/com/android/cts/net/hostside/DozeModeNonMeteredTest.java
rename to tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/DozeModeNonMeteredTest.java
index 1e89f15..f343df5 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/DozeModeNonMeteredTest.java
+++ b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/DozeModeNonMeteredTest.java
@@ -14,9 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.cts.net.hostside;
+package com.android.cts.netpolicy.hostside;
 
-import static com.android.cts.net.hostside.Property.NON_METERED_NETWORK;
+import static com.android.cts.netpolicy.hostside.Property.NON_METERED_NETWORK;
 
 @RequiredProperties({NON_METERED_NETWORK})
 public class DozeModeNonMeteredTest extends AbstractDozeModeTestCase {
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/DumpOnFailureRule.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/DumpOnFailureRule.java
similarity index 92%
rename from tests/cts/hostside/app/src/com/android/cts/net/hostside/DumpOnFailureRule.java
rename to tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/DumpOnFailureRule.java
index 07434b1..2dc6cc4 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/DumpOnFailureRule.java
+++ b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/DumpOnFailureRule.java
@@ -13,11 +13,11 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.cts.net.hostside;
+package com.android.cts.netpolicy.hostside;
 
-import static com.android.cts.net.hostside.AbstractRestrictBackgroundNetworkTestCase.TAG;
-import static com.android.cts.net.hostside.AbstractRestrictBackgroundNetworkTestCase.TEST_APP2_PKG;
-import static com.android.cts.net.hostside.AbstractRestrictBackgroundNetworkTestCase.TEST_PKG;
+import static com.android.cts.netpolicy.hostside.AbstractRestrictBackgroundNetworkTestCase.TAG;
+import static com.android.cts.netpolicy.hostside.AbstractRestrictBackgroundNetworkTestCase.TEST_APP2_PKG;
+import static com.android.cts.netpolicy.hostside.AbstractRestrictBackgroundNetworkTestCase.TEST_PKG;
 
 import android.os.Environment;
 import android.os.FileUtils;
@@ -42,7 +42,7 @@
 
 public class DumpOnFailureRule extends OnFailureRule {
     private File mDumpDir = new File(Environment.getExternalStorageDirectory(),
-            "CtsHostsideNetworkTests");
+            "CtsHostsideNetworkPolicyTests");
 
     @Override
     public void onTestFailure(Statement base, Description description, Throwable throwable) {
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/ExpeditedJobMeteredTest.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/ExpeditedJobMeteredTest.java
similarity index 85%
rename from tests/cts/hostside/app/src/com/android/cts/net/hostside/ExpeditedJobMeteredTest.java
rename to tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/ExpeditedJobMeteredTest.java
index 3809534..d56a50b 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/ExpeditedJobMeteredTest.java
+++ b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/ExpeditedJobMeteredTest.java
@@ -14,9 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.cts.net.hostside;
+package com.android.cts.netpolicy.hostside;
 
-import static com.android.cts.net.hostside.Property.METERED_NETWORK;
+import static com.android.cts.netpolicy.hostside.Property.METERED_NETWORK;
 
 @RequiredProperties({METERED_NETWORK})
 public class ExpeditedJobMeteredTest extends AbstractExpeditedJobTest {
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/ExpeditedJobNonMeteredTest.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/ExpeditedJobNonMeteredTest.java
similarity index 85%
rename from tests/cts/hostside/app/src/com/android/cts/net/hostside/ExpeditedJobNonMeteredTest.java
rename to tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/ExpeditedJobNonMeteredTest.java
index 6596269..0a776ee 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/ExpeditedJobNonMeteredTest.java
+++ b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/ExpeditedJobNonMeteredTest.java
@@ -14,9 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.cts.net.hostside;
+package com.android.cts.netpolicy.hostside;
 
-import static com.android.cts.net.hostside.Property.NON_METERED_NETWORK;
+import static com.android.cts.netpolicy.hostside.Property.NON_METERED_NETWORK;
 
 @RequiredProperties({NON_METERED_NETWORK})
 public class ExpeditedJobNonMeteredTest extends AbstractExpeditedJobTest {
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/MeterednessConfigurationRule.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/MeterednessConfigurationRule.java
similarity index 86%
rename from tests/cts/hostside/app/src/com/android/cts/net/hostside/MeterednessConfigurationRule.java
rename to tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/MeterednessConfigurationRule.java
index 5c99c67..4f4e68e 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/MeterednessConfigurationRule.java
+++ b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/MeterednessConfigurationRule.java
@@ -13,11 +13,11 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.cts.net.hostside;
+package com.android.cts.netpolicy.hostside;
 
-import static com.android.cts.net.hostside.NetworkPolicyTestUtils.setupActiveNetworkMeteredness;
-import static com.android.cts.net.hostside.Property.METERED_NETWORK;
-import static com.android.cts.net.hostside.Property.NON_METERED_NETWORK;
+import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.setupActiveNetworkMeteredness;
+import static com.android.cts.netpolicy.hostside.Property.METERED_NETWORK;
+import static com.android.cts.netpolicy.hostside.Property.NON_METERED_NETWORK;
 
 import android.util.ArraySet;
 
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/MixedModesTest.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/MixedModesTest.java
similarity index 95%
rename from tests/cts/hostside/app/src/com/android/cts/net/hostside/MixedModesTest.java
rename to tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/MixedModesTest.java
index c9edda6..b0fa106 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/MixedModesTest.java
+++ b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/MixedModesTest.java
@@ -13,15 +13,15 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.cts.net.hostside;
+package com.android.cts.netpolicy.hostside;
 
-import static com.android.cts.net.hostside.NetworkPolicyTestUtils.setRestrictBackground;
-import static com.android.cts.net.hostside.Property.APP_STANDBY_MODE;
-import static com.android.cts.net.hostside.Property.BATTERY_SAVER_MODE;
-import static com.android.cts.net.hostside.Property.DATA_SAVER_MODE;
-import static com.android.cts.net.hostside.Property.DOZE_MODE;
-import static com.android.cts.net.hostside.Property.METERED_NETWORK;
-import static com.android.cts.net.hostside.Property.NON_METERED_NETWORK;
+import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.setRestrictBackground;
+import static com.android.cts.netpolicy.hostside.Property.APP_STANDBY_MODE;
+import static com.android.cts.netpolicy.hostside.Property.BATTERY_SAVER_MODE;
+import static com.android.cts.netpolicy.hostside.Property.DATA_SAVER_MODE;
+import static com.android.cts.netpolicy.hostside.Property.DOZE_MODE;
+import static com.android.cts.netpolicy.hostside.Property.METERED_NETWORK;
+import static com.android.cts.netpolicy.hostside.Property.NON_METERED_NETWORK;
 
 import android.os.SystemClock;
 import android.util.Log;
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/MyNotificationListenerService.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/MyNotificationListenerService.java
similarity index 98%
rename from tests/cts/hostside/app/src/com/android/cts/net/hostside/MyNotificationListenerService.java
rename to tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/MyNotificationListenerService.java
index 0132536..6dc9921 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/MyNotificationListenerService.java
+++ b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/MyNotificationListenerService.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.cts.net.hostside;
+package com.android.cts.netpolicy.hostside;
 
 import android.app.Notification;
 import android.app.PendingIntent;
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/MyServiceClient.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/MyServiceClient.java
similarity index 98%
rename from tests/cts/hostside/app/src/com/android/cts/net/hostside/MyServiceClient.java
rename to tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/MyServiceClient.java
index 494192f..71b28f6 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/MyServiceClient.java
+++ b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/MyServiceClient.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.cts.net.hostside;
+package com.android.cts.netpolicy.hostside;
 
 import android.app.job.JobInfo;
 import android.content.ComponentName;
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkCallbackTest.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/NetworkCallbackTest.java
similarity index 96%
rename from tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkCallbackTest.java
rename to tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/NetworkCallbackTest.java
index 5552b8f..7038d02 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkCallbackTest.java
+++ b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/NetworkCallbackTest.java
@@ -14,18 +14,18 @@
  * limitations under the License.
  */
 
-package com.android.cts.net.hostside;
+package com.android.cts.netpolicy.hostside;
 
 import static android.app.ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
 import static android.app.ActivityManager.PROCESS_STATE_TOP_SLEEPING;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
 import static android.net.NetworkCapabilities.SIGNAL_STRENGTH_UNSPECIFIED;
 
-import static com.android.cts.net.hostside.NetworkPolicyTestUtils.canChangeActiveNetworkMeteredness;
-import static com.android.cts.net.hostside.NetworkPolicyTestUtils.getActiveNetworkCapabilities;
-import static com.android.cts.net.hostside.NetworkPolicyTestUtils.setRestrictBackground;
-import static com.android.cts.net.hostside.Property.BATTERY_SAVER_MODE;
-import static com.android.cts.net.hostside.Property.DATA_SAVER_MODE;
+import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.canChangeActiveNetworkMeteredness;
+import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.getActiveNetworkCapabilities;
+import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.setRestrictBackground;
+import static com.android.cts.netpolicy.hostside.Property.BATTERY_SAVER_MODE;
+import static com.android.cts.netpolicy.hostside.Property.DATA_SAVER_MODE;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyManagerTest.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/NetworkPolicyManagerTest.java
similarity index 95%
rename from tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyManagerTest.java
rename to tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/NetworkPolicyManagerTest.java
index 968e270..9b3fe9f 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyManagerTest.java
+++ b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/NetworkPolicyManagerTest.java
@@ -14,18 +14,18 @@
  * limitations under the License.
  */
 
-package com.android.cts.net.hostside;
+package com.android.cts.netpolicy.hostside;
 
 import static android.app.ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
 import static android.app.ActivityManager.PROCESS_STATE_TOP_SLEEPING;
 import static android.os.Process.SYSTEM_UID;
 
-import static com.android.cts.net.hostside.NetworkPolicyTestUtils.assertIsUidRestrictedOnMeteredNetworks;
-import static com.android.cts.net.hostside.NetworkPolicyTestUtils.assertNetworkingBlockedStatusForUid;
-import static com.android.cts.net.hostside.NetworkPolicyTestUtils.isUidNetworkingBlocked;
-import static com.android.cts.net.hostside.NetworkPolicyTestUtils.setRestrictBackground;
-import static com.android.cts.net.hostside.Property.BATTERY_SAVER_MODE;
-import static com.android.cts.net.hostside.Property.DATA_SAVER_MODE;
+import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.assertIsUidRestrictedOnMeteredNetworks;
+import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.assertNetworkingBlockedStatusForUid;
+import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.isUidNetworkingBlocked;
+import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.setRestrictBackground;
+import static com.android.cts.netpolicy.hostside.Property.BATTERY_SAVER_MODE;
+import static com.android.cts.netpolicy.hostside.Property.DATA_SAVER_MODE;
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyTestRunner.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/NetworkPolicyTestRunner.java
similarity index 96%
rename from tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyTestRunner.java
rename to tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/NetworkPolicyTestRunner.java
index f340907..0207b00 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyTestRunner.java
+++ b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/NetworkPolicyTestRunner.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.cts.net.hostside;
+package com.android.cts.netpolicy.hostside;
 
 import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner;
 
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyTestUtils.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/NetworkPolicyTestUtils.java
similarity index 98%
rename from tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyTestUtils.java
rename to tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/NetworkPolicyTestUtils.java
index 5331601..26a88f2 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyTestUtils.java
+++ b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/NetworkPolicyTestUtils.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.cts.net.hostside;
+package com.android.cts.netpolicy.hostside;
 
 import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_DISABLED;
 import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_ENABLED;
@@ -26,7 +26,7 @@
 import static android.net.wifi.WifiConfiguration.METERED_OVERRIDE_NONE;
 
 import static com.android.compatibility.common.util.SystemUtil.runShellCommandOrThrow;
-import static com.android.cts.net.hostside.AbstractRestrictBackgroundNetworkTestCase.TAG;
+import static com.android.cts.netpolicy.hostside.AbstractRestrictBackgroundNetworkTestCase.TAG;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/Property.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/Property.java
similarity index 72%
rename from tests/cts/hostside/app/src/com/android/cts/net/hostside/Property.java
rename to tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/Property.java
index 18805f9..a03833f 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/Property.java
+++ b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/Property.java
@@ -13,15 +13,15 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.cts.net.hostside;
+package com.android.cts.netpolicy.hostside;
 
-import static com.android.cts.net.hostside.NetworkPolicyTestUtils.canChangeActiveNetworkMeteredness;
-import static com.android.cts.net.hostside.NetworkPolicyTestUtils.isActiveNetworkMetered;
-import static com.android.cts.net.hostside.NetworkPolicyTestUtils.isAppStandbySupported;
-import static com.android.cts.net.hostside.NetworkPolicyTestUtils.isBatterySaverSupported;
-import static com.android.cts.net.hostside.NetworkPolicyTestUtils.isDataSaverSupported;
-import static com.android.cts.net.hostside.NetworkPolicyTestUtils.isDozeModeSupported;
-import static com.android.cts.net.hostside.NetworkPolicyTestUtils.isLowRamDevice;
+import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.canChangeActiveNetworkMeteredness;
+import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.isActiveNetworkMetered;
+import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.isAppStandbySupported;
+import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.isBatterySaverSupported;
+import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.isDataSaverSupported;
+import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.isDozeModeSupported;
+import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.isLowRamDevice;
 
 public enum Property {
     BATTERY_SAVER_MODE(1 << 0) {
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/RequiredProperties.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/RequiredProperties.java
similarity index 95%
rename from tests/cts/hostside/app/src/com/android/cts/net/hostside/RequiredProperties.java
rename to tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/RequiredProperties.java
index 96838bb..799a513 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/RequiredProperties.java
+++ b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/RequiredProperties.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.cts.net.hostside;
+package com.android.cts.netpolicy.hostside;
 
 import static java.lang.annotation.ElementType.METHOD;
 import static java.lang.annotation.ElementType.TYPE;
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/RequiredPropertiesRule.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/RequiredPropertiesRule.java
similarity index 95%
rename from tests/cts/hostside/app/src/com/android/cts/net/hostside/RequiredPropertiesRule.java
rename to tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/RequiredPropertiesRule.java
index 01f9f3e..5dea67c 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/RequiredPropertiesRule.java
+++ b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/RequiredPropertiesRule.java
@@ -13,9 +13,9 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.cts.net.hostside;
+package com.android.cts.netpolicy.hostside;
 
-import static com.android.cts.net.hostside.AbstractRestrictBackgroundNetworkTestCase.TAG;
+import static com.android.cts.netpolicy.hostside.AbstractRestrictBackgroundNetworkTestCase.TAG;
 
 import android.text.TextUtils;
 import android.util.ArraySet;
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/RestrictedModeTest.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/RestrictedModeTest.java
similarity index 97%
rename from tests/cts/hostside/app/src/com/android/cts/net/hostside/RestrictedModeTest.java
rename to tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/RestrictedModeTest.java
index 4777bf4..f183f4e 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/RestrictedModeTest.java
+++ b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/RestrictedModeTest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.cts.net.hostside;
+package com.android.cts.netpolicy.hostside;
 
 import org.junit.After;
 import org.junit.Before;
diff --git a/tests/cts/hostside-network-policy/app2/Android.bp b/tests/cts/hostside-network-policy/app2/Android.bp
new file mode 100644
index 0000000..6ef0b06
--- /dev/null
+++ b/tests/cts/hostside-network-policy/app2/Android.bp
@@ -0,0 +1,39 @@
+//
+// Copyright (C) 2024 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_team: "trendy_team_framework_backstage_power",
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+    name: "CtsHostsideNetworkPolicyTestsApp2",
+    defaults: ["cts_support_defaults"],
+    platform_apis: true,
+    static_libs: [
+        "androidx.annotation_annotation",
+        "CtsHostsideNetworkPolicyTestsAidl",
+        "modules-utils-build",
+    ],
+    srcs: ["src/**/*.java"],
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+        "sts",
+    ],
+    certificate: ":cts-netpolicy-app",
+}
diff --git a/tests/cts/hostside-network-policy/app2/AndroidManifest.xml b/tests/cts/hostside-network-policy/app2/AndroidManifest.xml
new file mode 100644
index 0000000..668f2da
--- /dev/null
+++ b/tests/cts/hostside-network-policy/app2/AndroidManifest.xml
@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2024 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.netpolicy.hostside.app2">
+
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE"/>
+    <uses-permission android:name="android.permission.INTERNET"/>
+    <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
+    <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
+
+    <!--
+     This application is used to listen to RESTRICT_BACKGROUND_CHANGED intents and store
+     them in a shared preferences which is then read by the test app. These broadcasts are
+     handled by 2 listeners, one defined the manifest and another dynamically registered by
+     a service.
+
+     The manifest-defined listener also handles ordered broadcasts used to share data with the
+     test app.
+
+     This application also provides a service, RemoteSocketFactoryService, that the test app can
+     use to open sockets to remote hosts as a different user ID.
+    -->
+    <application android:usesCleartextTraffic="true"
+            android:testOnly="true"
+            android:debuggable="true">
+
+        <activity android:name=".MyActivity"
+             android:exported="true"/>
+        <service android:name=".MyService"
+             android:exported="true"/>
+        <service android:name=".MyForegroundService"
+             android:foregroundServiceType="specialUse"
+             android:exported="true">
+            <property android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
+                      android:value="Connectivity" />
+        </service>
+        <receiver android:name=".MyBroadcastReceiver"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.net.conn.RESTRICT_BACKGROUND_CHANGED"/>
+                <action android:name="com.android.cts.netpolicy.hostside.app2.action.GET_COUNTERS"/>
+                <action android:name="com.android.cts.netpolicy.hostside.app2.action.GET_RESTRICT_BACKGROUND_STATUS"/>
+                <action android:name="com.android.cts.netpolicy.hostside.app2.action.CHECK_NETWORK"/>
+                <action android:name="com.android.cts.netpolicy.hostside.app2.action.SEND_NOTIFICATION"/>
+                <action android:name="com.android.cts.netpolicy.hostside.app2.action.SHOW_TOAST"/>
+                </intent-filter>
+        </receiver>
+        <service android:name=".MyJobService"
+            android:permission="android.permission.BIND_JOB_SERVICE" />
+    </application>
+
+    <!--
+      Adding this to make sure that receiving the broadcast is not restricted by
+      package visibility restrictions.
+    -->
+    <queries>
+        <package android:name="android" />
+    </queries>
+
+</manifest>
diff --git a/tests/cts/hostside/app2/res/drawable/ic_notification.png b/tests/cts/hostside-network-policy/app2/res/drawable/ic_notification.png
similarity index 100%
rename from tests/cts/hostside/app2/res/drawable/ic_notification.png
rename to tests/cts/hostside-network-policy/app2/res/drawable/ic_notification.png
Binary files differ
diff --git a/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/Common.java b/tests/cts/hostside-network-policy/app2/src/com/android/cts/netpolicy/hostside/app2/Common.java
similarity index 91%
rename from tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/Common.java
rename to tests/cts/hostside-network-policy/app2/src/com/android/cts/netpolicy/hostside/app2/Common.java
index 1c45579..1719f9b 100644
--- a/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/Common.java
+++ b/tests/cts/hostside-network-policy/app2/src/com/android/cts/netpolicy/hostside/app2/Common.java
@@ -13,11 +13,11 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.cts.net.hostside.app2;
+package com.android.cts.netpolicy.hostside.app2;
 
-import static com.android.cts.net.hostside.INetworkStateObserver.RESULT_ERROR_OTHER;
-import static com.android.cts.net.hostside.INetworkStateObserver.RESULT_ERROR_UNEXPECTED_CAPABILITIES;
-import static com.android.cts.net.hostside.INetworkStateObserver.RESULT_ERROR_UNEXPECTED_PROC_STATE;
+import static com.android.cts.netpolicy.hostside.INetworkStateObserver.RESULT_ERROR_OTHER;
+import static com.android.cts.netpolicy.hostside.INetworkStateObserver.RESULT_ERROR_UNEXPECTED_CAPABILITIES;
+import static com.android.cts.netpolicy.hostside.INetworkStateObserver.RESULT_ERROR_UNEXPECTED_PROC_STATE;
 
 import android.app.ActivityManager;
 import android.content.Context;
@@ -31,8 +31,8 @@
 import android.os.RemoteException;
 import android.util.Log;
 
-import com.android.cts.net.hostside.INetworkStateObserver;
-import com.android.cts.net.hostside.NetworkCheckResult;
+import com.android.cts.netpolicy.hostside.INetworkStateObserver;
+import com.android.cts.netpolicy.hostside.NetworkCheckResult;
 
 import java.net.HttpURLConnection;
 import java.net.InetAddress;
@@ -49,13 +49,13 @@
     static final String DYNAMIC_RECEIVER = "DynamicReceiver";
 
     static final String ACTION_RECEIVER_READY =
-            "com.android.cts.net.hostside.app2.action.RECEIVER_READY";
+            "com.android.cts.netpolicy.hostside.app2.action.RECEIVER_READY";
     static final String ACTION_FINISH_ACTIVITY =
-            "com.android.cts.net.hostside.app2.action.FINISH_ACTIVITY";
+            "com.android.cts.netpolicy.hostside.app2.action.FINISH_ACTIVITY";
     static final String ACTION_FINISH_JOB =
-            "com.android.cts.net.hostside.app2.action.FINISH_JOB";
+            "com.android.cts.netpolicy.hostside.app2.action.FINISH_JOB";
     static final String ACTION_SHOW_TOAST =
-            "com.android.cts.net.hostside.app2.action.SHOW_TOAST";
+            "com.android.cts.netpolicy.hostside.app2.action.SHOW_TOAST";
     // Copied from com.android.server.net.NetworkPolicyManagerService class
     static final String ACTION_SNOOZE_WARNING =
             "com.android.server.net.action.SNOOZE_WARNING";
@@ -71,7 +71,7 @@
     static final String NOTIFICATION_TYPE_ACTION_BUNDLE = "ACTION_BUNDLE";
     static final String NOTIFICATION_TYPE_ACTION_REMOTE_INPUT = "ACTION_REMOTE_INPUT";
 
-    static final String TEST_PKG = "com.android.cts.net.hostside";
+    static final String TEST_PKG = "com.android.cts.netpolicy.hostside";
     static final String KEY_NETWORK_STATE_OBSERVER = TEST_PKG + ".observer";
     static final String KEY_SKIP_VALIDATION_CHECKS = TEST_PKG + ".skip_validation_checks";
     static final String KEY_CUSTOM_URL =  TEST_PKG + ".custom_url";
diff --git a/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyActivity.java b/tests/cts/hostside-network-policy/app2/src/com/android/cts/netpolicy/hostside/app2/MyActivity.java
similarity index 90%
rename from tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyActivity.java
rename to tests/cts/hostside-network-policy/app2/src/com/android/cts/netpolicy/hostside/app2/MyActivity.java
index aa58ff9..d274c50 100644
--- a/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyActivity.java
+++ b/tests/cts/hostside-network-policy/app2/src/com/android/cts/netpolicy/hostside/app2/MyActivity.java
@@ -13,11 +13,11 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.cts.net.hostside.app2;
+package com.android.cts.netpolicy.hostside.app2;
 
-import static com.android.cts.net.hostside.app2.Common.ACTION_FINISH_ACTIVITY;
-import static com.android.cts.net.hostside.app2.Common.TAG;
-import static com.android.cts.net.hostside.app2.Common.TYPE_COMPONENT_ACTIVTY;
+import static com.android.cts.netpolicy.hostside.app2.Common.ACTION_FINISH_ACTIVITY;
+import static com.android.cts.netpolicy.hostside.app2.Common.TAG;
+import static com.android.cts.netpolicy.hostside.app2.Common.TYPE_COMPONENT_ACTIVTY;
 
 import android.app.Activity;
 import android.content.BroadcastReceiver;
diff --git a/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyBroadcastReceiver.java b/tests/cts/hostside-network-policy/app2/src/com/android/cts/netpolicy/hostside/app2/MyBroadcastReceiver.java
similarity index 86%
rename from tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyBroadcastReceiver.java
rename to tests/cts/hostside-network-policy/app2/src/com/android/cts/netpolicy/hostside/app2/MyBroadcastReceiver.java
index 1fd3745..27aec8c 100644
--- a/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyBroadcastReceiver.java
+++ b/tests/cts/hostside-network-policy/app2/src/com/android/cts/netpolicy/hostside/app2/MyBroadcastReceiver.java
@@ -14,22 +14,22 @@
  * limitations under the License.
  */
 
-package com.android.cts.net.hostside.app2;
+package com.android.cts.netpolicy.hostside.app2;
 
 import static android.net.ConnectivityManager.ACTION_RESTRICT_BACKGROUND_CHANGED;
 
-import static com.android.cts.net.hostside.app2.Common.ACTION_RECEIVER_READY;
-import static com.android.cts.net.hostside.app2.Common.ACTION_SHOW_TOAST;
-import static com.android.cts.net.hostside.app2.Common.ACTION_SNOOZE_WARNING;
-import static com.android.cts.net.hostside.app2.Common.MANIFEST_RECEIVER;
-import static com.android.cts.net.hostside.app2.Common.NOTIFICATION_TYPE_ACTION;
-import static com.android.cts.net.hostside.app2.Common.NOTIFICATION_TYPE_ACTION_BUNDLE;
-import static com.android.cts.net.hostside.app2.Common.NOTIFICATION_TYPE_ACTION_REMOTE_INPUT;
-import static com.android.cts.net.hostside.app2.Common.NOTIFICATION_TYPE_BUNDLE;
-import static com.android.cts.net.hostside.app2.Common.NOTIFICATION_TYPE_CONTENT;
-import static com.android.cts.net.hostside.app2.Common.NOTIFICATION_TYPE_DELETE;
-import static com.android.cts.net.hostside.app2.Common.NOTIFICATION_TYPE_FULL_SCREEN;
-import static com.android.cts.net.hostside.app2.Common.TAG;
+import static com.android.cts.netpolicy.hostside.app2.Common.ACTION_RECEIVER_READY;
+import static com.android.cts.netpolicy.hostside.app2.Common.ACTION_SHOW_TOAST;
+import static com.android.cts.netpolicy.hostside.app2.Common.ACTION_SNOOZE_WARNING;
+import static com.android.cts.netpolicy.hostside.app2.Common.MANIFEST_RECEIVER;
+import static com.android.cts.netpolicy.hostside.app2.Common.NOTIFICATION_TYPE_ACTION;
+import static com.android.cts.netpolicy.hostside.app2.Common.NOTIFICATION_TYPE_ACTION_BUNDLE;
+import static com.android.cts.netpolicy.hostside.app2.Common.NOTIFICATION_TYPE_ACTION_REMOTE_INPUT;
+import static com.android.cts.netpolicy.hostside.app2.Common.NOTIFICATION_TYPE_BUNDLE;
+import static com.android.cts.netpolicy.hostside.app2.Common.NOTIFICATION_TYPE_CONTENT;
+import static com.android.cts.netpolicy.hostside.app2.Common.NOTIFICATION_TYPE_DELETE;
+import static com.android.cts.netpolicy.hostside.app2.Common.NOTIFICATION_TYPE_FULL_SCREEN;
+import static com.android.cts.netpolicy.hostside.app2.Common.TAG;
 
 import android.app.Notification;
 import android.app.Notification.Action;
diff --git a/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyForegroundService.java b/tests/cts/hostside-network-policy/app2/src/com/android/cts/netpolicy/hostside/app2/MyForegroundService.java
similarity index 87%
rename from tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyForegroundService.java
rename to tests/cts/hostside-network-policy/app2/src/com/android/cts/netpolicy/hostside/app2/MyForegroundService.java
index b55761c..54cee3c 100644
--- a/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyForegroundService.java
+++ b/tests/cts/hostside-network-policy/app2/src/com/android/cts/netpolicy/hostside/app2/MyForegroundService.java
@@ -13,11 +13,11 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.cts.net.hostside.app2;
+package com.android.cts.netpolicy.hostside.app2;
 
-import static com.android.cts.net.hostside.app2.Common.TAG;
-import static com.android.cts.net.hostside.app2.Common.TEST_PKG;
-import static com.android.cts.net.hostside.app2.Common.TYPE_COMPONENT_FOREGROUND_SERVICE;
+import static com.android.cts.netpolicy.hostside.app2.Common.TAG;
+import static com.android.cts.netpolicy.hostside.app2.Common.TEST_PKG;
+import static com.android.cts.netpolicy.hostside.app2.Common.TYPE_COMPONENT_FOREGROUND_SERVICE;
 
 import android.R;
 import android.app.Notification;
@@ -31,7 +31,7 @@
 import android.os.RemoteException;
 import android.util.Log;
 
-import com.android.cts.net.hostside.INetworkStateObserver;
+import com.android.cts.netpolicy.hostside.INetworkStateObserver;
 
 /**
  * Service used to change app state to FOREGROUND_SERVICE.
diff --git a/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyJobService.java b/tests/cts/hostside-network-policy/app2/src/com/android/cts/netpolicy/hostside/app2/MyJobService.java
similarity index 90%
rename from tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyJobService.java
rename to tests/cts/hostside-network-policy/app2/src/com/android/cts/netpolicy/hostside/app2/MyJobService.java
index 8c112b6..eba55ed 100644
--- a/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyJobService.java
+++ b/tests/cts/hostside-network-policy/app2/src/com/android/cts/netpolicy/hostside/app2/MyJobService.java
@@ -13,11 +13,11 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.cts.net.hostside.app2;
+package com.android.cts.netpolicy.hostside.app2;
 
-import static com.android.cts.net.hostside.app2.Common.ACTION_FINISH_JOB;
-import static com.android.cts.net.hostside.app2.Common.TAG;
-import static com.android.cts.net.hostside.app2.Common.TYPE_COMPONENT_EXPEDITED_JOB;
+import static com.android.cts.netpolicy.hostside.app2.Common.ACTION_FINISH_JOB;
+import static com.android.cts.netpolicy.hostside.app2.Common.TAG;
+import static com.android.cts.netpolicy.hostside.app2.Common.TYPE_COMPONENT_EXPEDITED_JOB;
 
 import android.app.job.JobParameters;
 import android.app.job.JobService;
diff --git a/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyService.java b/tests/cts/hostside-network-policy/app2/src/com/android/cts/netpolicy/hostside/app2/MyService.java
similarity index 92%
rename from tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyService.java
rename to tests/cts/hostside-network-policy/app2/src/com/android/cts/netpolicy/hostside/app2/MyService.java
index 5010234..71bcead 100644
--- a/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyService.java
+++ b/tests/cts/hostside-network-policy/app2/src/com/android/cts/netpolicy/hostside/app2/MyService.java
@@ -13,14 +13,14 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.cts.net.hostside.app2;
+package com.android.cts.netpolicy.hostside.app2;
 
 import static android.net.ConnectivityManager.ACTION_RESTRICT_BACKGROUND_CHANGED;
 
-import static com.android.cts.net.hostside.app2.Common.ACTION_RECEIVER_READY;
-import static com.android.cts.net.hostside.app2.Common.ACTION_SNOOZE_WARNING;
-import static com.android.cts.net.hostside.app2.Common.DYNAMIC_RECEIVER;
-import static com.android.cts.net.hostside.app2.Common.TAG;
+import static com.android.cts.netpolicy.hostside.app2.Common.ACTION_RECEIVER_READY;
+import static com.android.cts.netpolicy.hostside.app2.Common.ACTION_SNOOZE_WARNING;
+import static com.android.cts.netpolicy.hostside.app2.Common.DYNAMIC_RECEIVER;
+import static com.android.cts.netpolicy.hostside.app2.Common.TAG;
 
 import android.app.NotificationChannel;
 import android.app.NotificationManager;
@@ -38,9 +38,9 @@
 import android.os.RemoteException;
 import android.util.Log;
 
-import com.android.cts.net.hostside.IMyService;
-import com.android.cts.net.hostside.INetworkCallback;
-import com.android.cts.net.hostside.NetworkCheckResult;
+import com.android.cts.netpolicy.hostside.IMyService;
+import com.android.cts.netpolicy.hostside.INetworkCallback;
+import com.android.cts.netpolicy.hostside.NetworkCheckResult;
 import com.android.modules.utils.build.SdkLevel;
 
 /**
diff --git a/tests/cts/hostside-network-policy/certs/Android.bp b/tests/cts/hostside-network-policy/certs/Android.bp
new file mode 100644
index 0000000..bfbc341
--- /dev/null
+++ b/tests/cts/hostside-network-policy/certs/Android.bp
@@ -0,0 +1,9 @@
+package {
+    default_team: "trendy_team_framework_backstage_power",
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_app_certificate {
+    name: "cts-netpolicy-app",
+    certificate: "cts-net-app",
+}
diff --git a/tests/cts/hostside/certs/README b/tests/cts/hostside-network-policy/certs/README
similarity index 100%
rename from tests/cts/hostside/certs/README
rename to tests/cts/hostside-network-policy/certs/README
diff --git a/tests/cts/hostside/certs/cts-net-app.pk8 b/tests/cts/hostside-network-policy/certs/cts-net-app.pk8
similarity index 100%
rename from tests/cts/hostside/certs/cts-net-app.pk8
rename to tests/cts/hostside-network-policy/certs/cts-net-app.pk8
Binary files differ
diff --git a/tests/cts/hostside/certs/cts-net-app.x509.pem b/tests/cts/hostside-network-policy/certs/cts-net-app.x509.pem
similarity index 100%
rename from tests/cts/hostside/certs/cts-net-app.x509.pem
rename to tests/cts/hostside-network-policy/certs/cts-net-app.x509.pem
diff --git a/tests/cts/hostside/instrumentation_arguments/Android.bp b/tests/cts/hostside-network-policy/instrumentation_arguments/Android.bp
similarity index 100%
rename from tests/cts/hostside/instrumentation_arguments/Android.bp
rename to tests/cts/hostside-network-policy/instrumentation_arguments/Android.bp
diff --git a/tests/cts/hostside/instrumentation_arguments/src/com/android/cts/net/arguments/InstrumentationArguments.java b/tests/cts/hostside-network-policy/instrumentation_arguments/src/com/android/cts/netpolicy/arguments/InstrumentationArguments.java
similarity index 94%
rename from tests/cts/hostside/instrumentation_arguments/src/com/android/cts/net/arguments/InstrumentationArguments.java
rename to tests/cts/hostside-network-policy/instrumentation_arguments/src/com/android/cts/netpolicy/arguments/InstrumentationArguments.java
index 911b129..0fe98e9 100644
--- a/tests/cts/hostside/instrumentation_arguments/src/com/android/cts/net/arguments/InstrumentationArguments.java
+++ b/tests/cts/hostside-network-policy/instrumentation_arguments/src/com/android/cts/netpolicy/arguments/InstrumentationArguments.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.cts.net.arguments;
+package com.android.cts.netpolicy.arguments;
 
 public interface InstrumentationArguments {
     String ARG_WAIVE_BIND_PRIORITY = "waive_bind_priority";
diff --git a/tests/cts/hostside/src/com/android/cts/net/HostsideConnOnActivityStartTest.java b/tests/cts/hostside-network-policy/src/com/android/cts/netpolicy/HostsideConnOnActivityStartTest.java
similarity index 91%
rename from tests/cts/hostside/src/com/android/cts/net/HostsideConnOnActivityStartTest.java
rename to tests/cts/hostside-network-policy/src/com/android/cts/netpolicy/HostsideConnOnActivityStartTest.java
index fff716d..422231d 100644
--- a/tests/cts/hostside/src/com/android/cts/net/HostsideConnOnActivityStartTest.java
+++ b/tests/cts/hostside-network-policy/src/com/android/cts/netpolicy/HostsideConnOnActivityStartTest.java
@@ -14,13 +14,12 @@
  * limitations under the License.
  */
 
-package com.android.cts.net;
+package com.android.cts.netpolicy;
 
-import static com.android.cts.net.arguments.InstrumentationArguments.ARG_WAIVE_BIND_PRIORITY;
+import static com.android.cts.netpolicy.arguments.InstrumentationArguments.ARG_WAIVE_BIND_PRIORITY;
 
 import android.platform.test.annotations.FlakyTest;
 
-import com.android.testutils.SkipPresubmit;
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.invoker.TestInformation;
 import com.android.tradefed.testtype.junit4.AfterClassWithInfo;
@@ -30,8 +29,8 @@
 
 import java.util.Map;
 
-@SkipPresubmit(reason = "Out of SLO flakiness")
-public class HostsideConnOnActivityStartTest extends HostsideNetworkTestCase {
+@FlakyTest(bugId = 288324467)
+public class HostsideConnOnActivityStartTest extends HostsideNetworkPolicyTestCase {
     private static final String TEST_CLASS = TEST_PKG + ".ConnOnActivityStartTest";
 
     @BeforeClassWithInfo
diff --git a/tests/cts/hostside/src/com/android/cts/net/HostsideDefaultNetworkRestrictionsTests.java b/tests/cts/hostside-network-policy/src/com/android/cts/netpolicy/HostsideDefaultNetworkRestrictionsTests.java
similarity index 92%
rename from tests/cts/hostside/src/com/android/cts/net/HostsideDefaultNetworkRestrictionsTests.java
rename to tests/cts/hostside-network-policy/src/com/android/cts/netpolicy/HostsideDefaultNetworkRestrictionsTests.java
index faabbef..62952bb 100644
--- a/tests/cts/hostside/src/com/android/cts/net/HostsideDefaultNetworkRestrictionsTests.java
+++ b/tests/cts/hostside-network-policy/src/com/android/cts/netpolicy/HostsideDefaultNetworkRestrictionsTests.java
@@ -14,11 +14,12 @@
  * limitations under the License.
  */
 
-package com.android.cts.net;
+package com.android.cts.netpolicy;
 
-import static com.android.cts.net.arguments.InstrumentationArguments.ARG_WAIVE_BIND_PRIORITY;
+import static com.android.cts.netpolicy.arguments.InstrumentationArguments.ARG_WAIVE_BIND_PRIORITY;
 
-import com.android.testutils.SkipPresubmit;
+import android.platform.test.annotations.FlakyTest;
+
 import com.android.tradefed.device.DeviceNotAvailableException;
 
 import org.junit.After;
@@ -28,8 +29,8 @@
 import java.util.Map;
 
 // TODO(b/321848487): Annotate with @RequiresFlagsEnabled to mirror the device-side tests.
-@SkipPresubmit(reason = "Monitoring for flakiness")
-public class HostsideDefaultNetworkRestrictionsTests extends HostsideNetworkTestCase {
+@FlakyTest(bugId = 288324467)
+public class HostsideDefaultNetworkRestrictionsTests extends HostsideNetworkPolicyTestCase {
     private static final String METERED_TEST_CLASS = TEST_PKG + ".DefaultRestrictionsMeteredTest";
     private static final String NON_METERED_TEST_CLASS =
             TEST_PKG + ".DefaultRestrictionsNonMeteredTest";
diff --git a/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkCallbackTests.java b/tests/cts/hostside-network-policy/src/com/android/cts/netpolicy/HostsideNetworkCallbackTests.java
similarity index 88%
rename from tests/cts/hostside/src/com/android/cts/net/HostsideNetworkCallbackTests.java
rename to tests/cts/hostside-network-policy/src/com/android/cts/netpolicy/HostsideNetworkCallbackTests.java
index c4bcdfd..2c2b118 100644
--- a/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkCallbackTests.java
+++ b/tests/cts/hostside-network-policy/src/com/android/cts/netpolicy/HostsideNetworkCallbackTests.java
@@ -13,11 +13,11 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.cts.net;
+package com.android.cts.netpolicy;
 
-import static com.android.cts.net.arguments.InstrumentationArguments.ARG_WAIVE_BIND_PRIORITY;
+import static com.android.cts.netpolicy.arguments.InstrumentationArguments.ARG_WAIVE_BIND_PRIORITY;
 
-import com.android.testutils.SkipPresubmit;
+import android.platform.test.annotations.FlakyTest;
 
 import org.junit.After;
 import org.junit.Before;
@@ -25,8 +25,8 @@
 
 import java.util.Map;
 
-@SkipPresubmit(reason = "Out of SLO flakiness")
-public class HostsideNetworkCallbackTests extends HostsideNetworkTestCase {
+@FlakyTest(bugId = 288324467)
+public class HostsideNetworkCallbackTests extends HostsideNetworkPolicyTestCase {
 
     @Before
     public void setUp() throws Exception {
diff --git a/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkPolicyManagerTests.java b/tests/cts/hostside-network-policy/src/com/android/cts/netpolicy/HostsideNetworkPolicyManagerTests.java
similarity index 94%
rename from tests/cts/hostside/src/com/android/cts/net/HostsideNetworkPolicyManagerTests.java
rename to tests/cts/hostside-network-policy/src/com/android/cts/netpolicy/HostsideNetworkPolicyManagerTests.java
index 4730b14..8ffe360 100644
--- a/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkPolicyManagerTests.java
+++ b/tests/cts/hostside-network-policy/src/com/android/cts/netpolicy/HostsideNetworkPolicyManagerTests.java
@@ -14,9 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.cts.net;
+package com.android.cts.netpolicy;
 
-import static com.android.cts.net.arguments.InstrumentationArguments.ARG_WAIVE_BIND_PRIORITY;
+import static com.android.cts.netpolicy.arguments.InstrumentationArguments.ARG_WAIVE_BIND_PRIORITY;
 
 import org.junit.After;
 import org.junit.Before;
@@ -24,7 +24,7 @@
 
 import java.util.Map;
 
-public class HostsideNetworkPolicyManagerTests extends HostsideNetworkTestCase {
+public class HostsideNetworkPolicyManagerTests extends HostsideNetworkPolicyTestCase {
     @Before
     public void setUp() throws Exception {
         uninstallPackage(TEST_APP2_PKG, false);
diff --git a/tests/cts/hostside-network-policy/src/com/android/cts/netpolicy/HostsideNetworkPolicyTestCase.java b/tests/cts/hostside-network-policy/src/com/android/cts/netpolicy/HostsideNetworkPolicyTestCase.java
new file mode 100644
index 0000000..6de6b17
--- /dev/null
+++ b/tests/cts/hostside-network-policy/src/com/android/cts/netpolicy/HostsideNetworkPolicyTestCase.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2024 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.netpolicy;
+
+import static com.android.cts.netpolicy.arguments.InstrumentationArguments.ARG_CONNECTION_CHECK_CUSTOM_URL;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
+
+import com.android.ddmlib.Log;
+import com.android.tradefed.config.Option;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.invoker.TestInformation;
+import com.android.tradefed.targetprep.BuildError;
+import com.android.tradefed.targetprep.TargetSetupError;
+import com.android.tradefed.targetprep.suite.SuiteApkInstaller;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.AfterClassWithInfo;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+import com.android.tradefed.testtype.junit4.BeforeClassWithInfo;
+import com.android.tradefed.testtype.junit4.DeviceTestRunOptions;
+import com.android.tradefed.util.RunUtil;
+
+import org.junit.runner.RunWith;
+
+import java.util.Map;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+abstract class HostsideNetworkPolicyTestCase extends BaseHostJUnit4Test {
+    protected static final boolean DEBUG = false;
+    protected static final String TAG = "HostsideNetworkPolicyTests";
+    protected static final String TEST_PKG = "com.android.cts.netpolicy.hostside";
+    protected static final String TEST_APK = "CtsHostsideNetworkPolicyTestsApp.apk";
+    protected static final String TEST_APP2_PKG = "com.android.cts.netpolicy.hostside.app2";
+    protected static final String TEST_APP2_APK = "CtsHostsideNetworkPolicyTestsApp2.apk";
+
+    @Option(name = "custom-url", importance = Option.Importance.IF_UNSET,
+            description = "A custom url to use for testing network connections")
+    protected String mCustomUrl;
+
+    @BeforeClassWithInfo
+    public static void setUpOnceBase(TestInformation testInfo) throws Exception {
+        uninstallPackage(testInfo, TEST_PKG, false);
+        installPackage(testInfo, TEST_APK);
+    }
+
+    @AfterClassWithInfo
+    public static void tearDownOnceBase(TestInformation testInfo)
+            throws DeviceNotAvailableException {
+        uninstallPackage(testInfo, TEST_PKG, true);
+    }
+
+    // Custom static method to install the specified package, this is used to bypass auto-cleanup
+    // per test in BaseHostJUnit4.
+    protected static void installPackage(TestInformation testInfo, String apk)
+            throws DeviceNotAvailableException, TargetSetupError {
+        assertNotNull(testInfo);
+        final int userId = testInfo.getDevice().getCurrentUser();
+        final SuiteApkInstaller installer = new SuiteApkInstaller();
+        // Force the apk clean up
+        installer.setCleanApk(true);
+        installer.addTestFileName(apk);
+        installer.setUserId(userId);
+        installer.setShouldGrantPermission(true);
+        installer.addInstallArg("-t");
+        try {
+            installer.setUp(testInfo);
+        } catch (BuildError e) {
+            throw new TargetSetupError(
+                    e.getMessage(), e, testInfo.getDevice().getDeviceDescriptor(), e.getErrorId());
+        }
+    }
+
+    protected void installPackage(String apk) throws DeviceNotAvailableException, TargetSetupError {
+        installPackage(getTestInformation(), apk);
+    }
+
+    protected static void uninstallPackage(TestInformation testInfo, String packageName,
+            boolean shouldSucceed)
+            throws DeviceNotAvailableException {
+        assertNotNull(testInfo);
+        final String result = testInfo.getDevice().uninstallPackage(packageName);
+        if (shouldSucceed) {
+            assertNull("uninstallPackage(" + packageName + ") failed: " + result, result);
+        }
+    }
+
+    protected void uninstallPackage(String packageName,
+            boolean shouldSucceed)
+            throws DeviceNotAvailableException {
+        uninstallPackage(getTestInformation(), packageName, shouldSucceed);
+    }
+
+    protected void assertPackageUninstalled(String packageName) throws DeviceNotAvailableException {
+        final String command = "cmd package list packages " + packageName;
+        final int max_tries = 5;
+        for (int i = 1; i <= max_tries; i++) {
+            final String result = runCommand(command);
+            if (result.trim().isEmpty()) {
+                return;
+            }
+            // 'list packages' filters by substring, so we need to iterate with the results
+            // and check one by one, otherwise 'com.android.cts.netpolicy.hostside' could return
+            // 'com.android.cts.netpolicy.hostside.app2'
+            boolean found = false;
+            for (String line : result.split("[\\r\\n]+")) {
+                if (line.endsWith(packageName)) {
+                    found = true;
+                    break;
+                }
+            }
+            if (!found) {
+                return;
+            }
+            Log.v(TAG, "Package " + packageName + " not uninstalled yet (" + result
+                    + "); sleeping 1s before polling again");
+            RunUtil.getDefault().sleep(1000);
+        }
+        fail("Package '" + packageName + "' not uinstalled after " + max_tries + " seconds");
+    }
+
+    protected int getUid(String packageName) throws DeviceNotAvailableException {
+        final int currentUser = getDevice().getCurrentUser();
+        final String uidLines = runCommand(
+                "cmd package list packages -U --user " + currentUser + " " + packageName);
+        for (String uidLine : uidLines.split("\n")) {
+            if (uidLine.startsWith("package:" + packageName + " uid:")) {
+                final String[] uidLineParts = uidLine.split(":");
+                // 3rd entry is package uid
+                return Integer.parseInt(uidLineParts[2].trim());
+            }
+        }
+        throw new IllegalStateException("Failed to find the test app on the device; pkg="
+                + packageName + ", u=" + currentUser);
+    }
+
+    protected boolean runDeviceTestsWithCustomOptions(String packageName, String className)
+            throws DeviceNotAvailableException {
+        return runDeviceTestsWithCustomOptions(packageName, className, null);
+    }
+
+    protected boolean runDeviceTestsWithCustomOptions(String packageName, String className,
+            String methodName) throws DeviceNotAvailableException {
+        return runDeviceTestsWithCustomOptions(packageName, className, methodName, null);
+    }
+
+    protected boolean runDeviceTestsWithCustomOptions(String packageName, String className,
+            String methodName, Map<String, String> testArgs) throws DeviceNotAvailableException {
+        final DeviceTestRunOptions deviceTestRunOptions = new DeviceTestRunOptions(packageName)
+                .setTestClassName(className)
+                .setTestMethodName(methodName);
+
+        // Currently there is only one custom option that the test exposes.
+        if (mCustomUrl != null) {
+            deviceTestRunOptions.addInstrumentationArg(ARG_CONNECTION_CHECK_CUSTOM_URL, mCustomUrl);
+        }
+        // Pass over any test specific arguments.
+        if (testArgs != null) {
+            for (Map.Entry<String, String> arg : testArgs.entrySet()) {
+                deviceTestRunOptions.addInstrumentationArg(arg.getKey(), arg.getValue());
+            }
+        }
+        return runDeviceTests(deviceTestRunOptions);
+    }
+
+    protected String runCommand(String command) throws DeviceNotAvailableException {
+        Log.d(TAG, "Command: '" + command + "'");
+        final String output = getDevice().executeShellCommand(command);
+        if (DEBUG) Log.v(TAG, "Output: " + output.trim());
+        return output;
+    }
+}
diff --git a/tests/cts/hostside/src/com/android/cts/net/HostsideRestrictBackgroundNetworkTests.java b/tests/cts/hostside-network-policy/src/com/android/cts/netpolicy/HostsideRestrictBackgroundNetworkTests.java
similarity index 98%
rename from tests/cts/hostside/src/com/android/cts/net/HostsideRestrictBackgroundNetworkTests.java
rename to tests/cts/hostside-network-policy/src/com/android/cts/netpolicy/HostsideRestrictBackgroundNetworkTests.java
index 7b9d3b5..0261c7d 100644
--- a/tests/cts/hostside/src/com/android/cts/net/HostsideRestrictBackgroundNetworkTests.java
+++ b/tests/cts/hostside-network-policy/src/com/android/cts/netpolicy/HostsideRestrictBackgroundNetworkTests.java
@@ -14,14 +14,14 @@
  * limitations under the License.
  */
 
-package com.android.cts.net;
+package com.android.cts.netpolicy;
 
 import static org.junit.Assert.fail;
 
+import android.platform.test.annotations.FlakyTest;
 import android.platform.test.annotations.SecurityTest;
 
 import com.android.ddmlib.Log;
-import com.android.testutils.SkipPresubmit;
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.util.RunUtil;
 
@@ -29,8 +29,8 @@
 import org.junit.Before;
 import org.junit.Test;
 
-@SkipPresubmit(reason = "Out of SLO flakiness")
-public class HostsideRestrictBackgroundNetworkTests extends HostsideNetworkTestCase {
+@FlakyTest(bugId = 288324467)
+public class HostsideRestrictBackgroundNetworkTests extends HostsideNetworkPolicyTestCase {
 
     @Before
     public void setUp() throws Exception {
diff --git a/tests/cts/hostside/src/com/android/cts/net/NetworkPolicyTestsPreparer.java b/tests/cts/hostside-network-policy/src/com/android/cts/netpolicy/NetworkPolicyTestsPreparer.java
similarity index 98%
rename from tests/cts/hostside/src/com/android/cts/net/NetworkPolicyTestsPreparer.java
rename to tests/cts/hostside-network-policy/src/com/android/cts/netpolicy/NetworkPolicyTestsPreparer.java
index 23aca24..cbf2f4d 100644
--- a/tests/cts/hostside/src/com/android/cts/net/NetworkPolicyTestsPreparer.java
+++ b/tests/cts/hostside-network-policy/src/com/android/cts/netpolicy/NetworkPolicyTestsPreparer.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.cts.net;
+package com.android.cts.netpolicy;
 
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.device.ITestDevice;
diff --git a/tests/cts/hostside/Android.bp b/tests/cts/hostside/Android.bp
index f6c0430..14d5d54 100644
--- a/tests/cts/hostside/Android.bp
+++ b/tests/cts/hostside/Android.bp
@@ -29,7 +29,6 @@
     // Only compile source java files in this apk.
     srcs: [
         "src/**/*.java",
-        ":ArgumentConstants",
     ],
     libs: [
         "net-tests-utils-host-device-common",
diff --git a/tests/cts/hostside/AndroidTest.xml b/tests/cts/hostside/AndroidTest.xml
index 0ffe81e..ea6b078 100644
--- a/tests/cts/hostside/AndroidTest.xml
+++ b/tests/cts/hostside/AndroidTest.xml
@@ -22,7 +22,6 @@
     <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
 
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.LocationCheck" />
-    <target_preparer class="com.android.cts.net.NetworkPolicyTestsPreparer" />
 
     <!-- Enabling change id ALLOW_TEST_API_ACCESS allows that package to access @TestApi methods -->
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
@@ -30,7 +29,6 @@
         <option name="teardown-command" value="am compat reset ALLOW_TEST_API_ACCESS com.android.cts.net.hostside.app2" />
         <option name="teardown-command" value="cmd power set-mode 0" />
         <option name="teardown-command" value="cmd battery reset" />
-        <option name="teardown-command" value="cmd netpolicy stop-watching" />
     </target_preparer>
 
     <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
diff --git a/tests/cts/hostside/OWNERS b/tests/cts/hostside/OWNERS
deleted file mode 100644
index 20bc55e..0000000
--- a/tests/cts/hostside/OWNERS
+++ /dev/null
@@ -1,4 +0,0 @@
-# Bug component: 61373
-# Inherits parent owners
-sudheersai@google.com
-jchalard@google.com
diff --git a/tests/cts/hostside/app/Android.bp b/tests/cts/hostside/app/Android.bp
index cf4afa9..919e025 100644
--- a/tests/cts/hostside/app/Android.bp
+++ b/tests/cts/hostside/app/Android.bp
@@ -38,7 +38,6 @@
     ],
     srcs: [
         "src/**/*.java",
-         ":ArgumentConstants",
     ],
     // Tag this module as a cts test artifact
     test_suites: [
diff --git a/tests/cts/hostside/app/AndroidManifest.xml b/tests/cts/hostside/app/AndroidManifest.xml
index ca3397b..e0f4cdc 100644
--- a/tests/cts/hostside/app/AndroidManifest.xml
+++ b/tests/cts/hostside/app/AndroidManifest.xml
@@ -43,14 +43,6 @@
                 <action android:name="android.net.VpnService"/>
             </intent-filter>
         </service>
-        <service android:name=".MyNotificationListenerService"
-             android:label="MyNotificationListenerService"
-             android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE"
-             android:exported="true">
-            <intent-filter>
-                <action android:name="android.service.notification.NotificationListenerService"/>
-            </intent-filter>
-        </service>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
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 8e7b3d4..50d6e76 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
@@ -124,6 +124,7 @@
 import com.android.net.module.util.CollectionUtils;
 import com.android.net.module.util.PacketBuilder;
 import com.android.testutils.AutoReleaseNetworkCallbackRule;
+import com.android.testutils.ConnectUtil;
 import com.android.testutils.DevSdkIgnoreRule;
 import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
 import com.android.testutils.RecorderCallback;
@@ -221,6 +222,7 @@
     private WifiManager mWifiManager;
     private RemoteSocketFactoryClient mRemoteSocketFactoryClient;
     private CtsNetUtils mCtsNetUtils;
+    private ConnectUtil mConnectUtil;
     private PackageManager mPackageManager;
     private Context mTestContext;
     private Context mTargetContext;
@@ -270,6 +272,7 @@
         mRemoteSocketFactoryClient.bind();
         mDevice.waitForIdle();
         mCtsNetUtils = new CtsNetUtils(mTestContext);
+        mConnectUtil = new ConnectUtil(mTestContext);
         mPackageManager = mTestContext.getPackageManager();
         assumeTrue(supportedHardware());
     }
@@ -893,7 +896,7 @@
         final boolean isWifiEnabled = mWifiManager.isWifiEnabled();
         testAndCleanup(() -> {
             // Ensure both of wifi and mobile data are connected.
-            final Network wifiNetwork = mCtsNetUtils.ensureWifiConnected();
+            final Network wifiNetwork = mConnectUtil.ensureWifiValidated();
             final Network cellNetwork = mNetworkCallbackRule.requestCell();
             // Store current default network.
             final Network defaultNetwork = mCM.getActiveNetwork();
@@ -1940,7 +1943,7 @@
                 .build();
         final CtsNetUtils.TestNetworkCallback callback = new CtsNetUtils.TestNetworkCallback();
         mCM.requestNetwork(request, callback);
-        final FileDescriptor srcTunFd = runWithShellPermissionIdentity(() -> {
+        final ParcelFileDescriptor srcTunFd = runWithShellPermissionIdentity(() -> {
             final TestNetworkManager tnm = mTestContext.getSystemService(TestNetworkManager.class);
             List<LinkAddress> linkAddresses = duplicatedAddress
                     ? List.of(new LinkAddress("192.0.2.2/24"),
@@ -1949,7 +1952,7 @@
                             new LinkAddress("2001:db8:3:4::ffe/64"));
             final TestNetworkInterface iface = tnm.createTunInterface(linkAddresses);
             tnm.setupTestNetwork(iface.getInterfaceName(), new Binder());
-            return iface.getFileDescriptor().getFileDescriptor();
+            return iface.getFileDescriptor();
         }, MANAGE_TEST_NETWORKS);
         final Network testNetwork = callback.waitForAvailable();
         assertNotNull(testNetwork);
@@ -1963,11 +1966,11 @@
                     false /* isAlwaysMetered */);
 
             final FileDescriptor dstUdpFd = dstSock.getFileDescriptor$();
-            checkBlockUdp(srcTunFd, dstUdpFd,
+            checkBlockUdp(srcTunFd.getFileDescriptor(), dstUdpFd,
                     InetAddresses.parseNumericAddress("192.0.2.2") /* dstAddress */,
                     InetAddresses.parseNumericAddress("192.0.2.1") /* srcAddress */,
                     duplicatedAddress ? EXPECT_PASS : EXPECT_BLOCK);
-            checkBlockUdp(srcTunFd, dstUdpFd,
+            checkBlockUdp(srcTunFd.getFileDescriptor(), dstUdpFd,
                     InetAddresses.parseNumericAddress("2001:db8:1:2::ffe") /* dstAddress */,
                     InetAddresses.parseNumericAddress("2001:db8:1:2::ffa") /* srcAddress */,
                     duplicatedAddress ? EXPECT_PASS : EXPECT_BLOCK);
@@ -1975,7 +1978,7 @@
             // Traffic on VPN should not be affected
             checkTrafficOnVpn();
         }, /* cleanup */ () -> {
-                Os.close(srcTunFd);
+                srcTunFd.close();
                 dstSock.close();
             }, /* cleanup */ () -> {
                 runWithShellPermissionIdentity(() -> {
diff --git a/tests/cts/hostside/app2/Android.bp b/tests/cts/hostside/app2/Android.bp
index c526172..ad25562 100644
--- a/tests/cts/hostside/app2/Android.bp
+++ b/tests/cts/hostside/app2/Android.bp
@@ -35,5 +35,4 @@
         "general-tests",
         "sts",
     ],
-    certificate: ":cts-net-app",
 }
diff --git a/tests/cts/hostside/app2/AndroidManifest.xml b/tests/cts/hostside/app2/AndroidManifest.xml
index 2c2d957..412b307 100644
--- a/tests/cts/hostside/app2/AndroidManifest.xml
+++ b/tests/cts/hostside/app2/AndroidManifest.xml
@@ -40,33 +40,8 @@
     <application android:usesCleartextTraffic="true"
             android:testOnly="true"
             android:debuggable="true">
-
-        <activity android:name=".MyActivity"
-             android:exported="true"/>
-        <service android:name=".MyService"
-             android:exported="true"/>
-        <service android:name=".MyForegroundService"
-             android:foregroundServiceType="specialUse"
-             android:exported="true">
-            <property android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE" 
-                      android:value="Connectivity" />
-        </service>
         <service android:name=".RemoteSocketFactoryService"
              android:exported="true"/>
-
-        <receiver android:name=".MyBroadcastReceiver"
-             android:exported="true">
-            <intent-filter>
-                <action android:name="android.net.conn.RESTRICT_BACKGROUND_CHANGED"/>
-                <action android:name="com.android.cts.net.hostside.app2.action.GET_COUNTERS"/>
-                <action android:name="com.android.cts.net.hostside.app2.action.GET_RESTRICT_BACKGROUND_STATUS"/>
-                <action android:name="com.android.cts.net.hostside.app2.action.CHECK_NETWORK"/>
-                <action android:name="com.android.cts.net.hostside.app2.action.SEND_NOTIFICATION"/>
-                <action android:name="com.android.cts.net.hostside.app2.action.SHOW_TOAST"/>
-                </intent-filter>
-        </receiver>
-        <service android:name=".MyJobService"
-            android:permission="android.permission.BIND_JOB_SERVICE" />
     </application>
 
     <!--
diff --git a/tests/cts/hostside/certs/Android.bp b/tests/cts/hostside/certs/Android.bp
deleted file mode 100644
index 301973e..0000000
--- a/tests/cts/hostside/certs/Android.bp
+++ /dev/null
@@ -1,9 +0,0 @@
-package {
-    default_team: "trendy_team_fwk_core_networking",
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-android_app_certificate {
-    name: "cts-net-app",
-    certificate: "cts-net-app",
-}
diff --git a/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkTestCase.java b/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkTestCase.java
index d7dfa80..69d61b3 100644
--- a/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkTestCase.java
+++ b/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkTestCase.java
@@ -16,15 +16,10 @@
 
 package com.android.cts.net;
 
-import static com.android.cts.net.arguments.InstrumentationArguments.ARG_CONNECTION_CHECK_CUSTOM_URL;
-
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
-import static org.junit.Assert.fail;
 
-import com.android.ddmlib.Log;
 import com.android.modules.utils.build.testing.DeviceSdkLevel;
-import com.android.tradefed.config.Option;
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.invoker.TestInformation;
 import com.android.tradefed.targetprep.BuildError;
@@ -34,27 +29,17 @@
 import com.android.tradefed.testtype.junit4.AfterClassWithInfo;
 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
 import com.android.tradefed.testtype.junit4.BeforeClassWithInfo;
-import com.android.tradefed.testtype.junit4.DeviceTestRunOptions;
-import com.android.tradefed.util.RunUtil;
 
 import org.junit.runner.RunWith;
 
-import java.util.Map;
-
 @RunWith(DeviceJUnit4ClassRunner.class)
 abstract class HostsideNetworkTestCase extends BaseHostJUnit4Test {
-    protected static final boolean DEBUG = false;
-    protected static final String TAG = "HostsideNetworkTests";
     protected static final String TEST_PKG = "com.android.cts.net.hostside";
     protected static final String TEST_APK = "CtsHostsideNetworkTestsApp.apk";
     protected static final String TEST_APK_NEXT = "CtsHostsideNetworkTestsAppNext.apk";
     protected static final String TEST_APP2_PKG = "com.android.cts.net.hostside.app2";
     protected static final String TEST_APP2_APK = "CtsHostsideNetworkTestsApp2.apk";
 
-    @Option(name = "custom-url", importance = Option.Importance.IF_UNSET,
-            description = "A custom url to use for testing network connections")
-    protected String mCustomUrl;
-
     @BeforeClassWithInfo
     public static void setUpOnceBase(TestInformation testInfo) throws Exception {
         DeviceSdkLevel deviceSdkLevel = new DeviceSdkLevel(testInfo.getDevice());
@@ -110,85 +95,4 @@
             throws DeviceNotAvailableException {
         uninstallPackage(getTestInformation(), packageName, shouldSucceed);
     }
-
-    protected void assertPackageUninstalled(String packageName) throws DeviceNotAvailableException,
-            InterruptedException {
-        final String command = "cmd package list packages " + packageName;
-        final int max_tries = 5;
-        for (int i = 1; i <= max_tries; i++) {
-            final String result = runCommand(command);
-            if (result.trim().isEmpty()) {
-                return;
-            }
-            // 'list packages' filters by substring, so we need to iterate with the results
-            // and check one by one, otherwise 'com.android.cts.net.hostside' could return
-            // 'com.android.cts.net.hostside.app2'
-            boolean found = false;
-            for (String line : result.split("[\\r\\n]+")) {
-                if (line.endsWith(packageName)) {
-                    found = true;
-                    break;
-                }
-            }
-            if (!found) {
-                return;
-            }
-            i++;
-            Log.v(TAG, "Package " + packageName + " not uninstalled yet (" + result
-                    + "); sleeping 1s before polling again");
-            RunUtil.getDefault().sleep(1000);
-        }
-        fail("Package '" + packageName + "' not uinstalled after " + max_tries + " seconds");
-    }
-
-    protected int getUid(String packageName) throws DeviceNotAvailableException {
-        final int currentUser = getDevice().getCurrentUser();
-        final String uidLines = runCommand(
-                "cmd package list packages -U --user " + currentUser + " " + packageName);
-        for (String uidLine : uidLines.split("\n")) {
-            if (uidLine.startsWith("package:" + packageName + " uid:")) {
-                final String[] uidLineParts = uidLine.split(":");
-                // 3rd entry is package uid
-                return Integer.parseInt(uidLineParts[2].trim());
-            }
-        }
-        throw new IllegalStateException("Failed to find the test app on the device; pkg="
-                + packageName + ", u=" + currentUser);
-    }
-
-    protected boolean runDeviceTestsWithCustomOptions(String packageName, String className)
-            throws DeviceNotAvailableException {
-        return runDeviceTestsWithCustomOptions(packageName, className, null);
-    }
-
-    protected boolean runDeviceTestsWithCustomOptions(String packageName, String className,
-            String methodName) throws DeviceNotAvailableException {
-        return runDeviceTestsWithCustomOptions(packageName, className, methodName, null);
-    }
-
-    protected boolean runDeviceTestsWithCustomOptions(String packageName, String className,
-            String methodName, Map<String, String> testArgs) throws DeviceNotAvailableException {
-        final DeviceTestRunOptions deviceTestRunOptions = new DeviceTestRunOptions(packageName)
-                .setTestClassName(className)
-                .setTestMethodName(methodName);
-
-        // Currently there is only one custom option that the test exposes.
-        if (mCustomUrl != null) {
-            deviceTestRunOptions.addInstrumentationArg(ARG_CONNECTION_CHECK_CUSTOM_URL, mCustomUrl);
-        }
-        // Pass over any test specific arguments.
-        if (testArgs != null) {
-            for (Map.Entry<String, String> arg : testArgs.entrySet()) {
-                deviceTestRunOptions.addInstrumentationArg(arg.getKey(), arg.getValue());
-            }
-        }
-        return runDeviceTests(deviceTestRunOptions);
-    }
-
-    protected String runCommand(String command) throws DeviceNotAvailableException {
-        Log.d(TAG, "Command: '" + command + "'");
-        final String output = getDevice().executeShellCommand(command);
-        if (DEBUG) Log.v(TAG, "Output: " + output.trim());
-        return output;
-    }
 }
diff --git a/tests/cts/multidevices/snippet/ConnectivityMultiDevicesSnippet.kt b/tests/cts/multidevices/snippet/ConnectivityMultiDevicesSnippet.kt
index c883b78..258648f 100644
--- a/tests/cts/multidevices/snippet/ConnectivityMultiDevicesSnippet.kt
+++ b/tests/cts/multidevices/snippet/ConnectivityMultiDevicesSnippet.kt
@@ -64,9 +64,7 @@
     fun hasTelephonyFeature() = pm.hasSystemFeature(FEATURE_TELEPHONY)
 
     @Rpc(description = "Check whether the device supporters AP + STA concurrency.")
-    fun isStaApConcurrencySupported() {
-        wifiManager.isStaApConcurrencySupported()
-    }
+    fun isStaApConcurrencySupported() = wifiManager.isStaApConcurrencySupported()
 
     @Rpc(description = "Request cellular connection and ensure it is the default network.")
     fun requestCellularAndEnsureDefault() {
diff --git a/tests/cts/net/native/src/BpfCompatTest.cpp b/tests/cts/net/native/src/BpfCompatTest.cpp
index 5c02b0d..e2fdd3d 100644
--- a/tests/cts/net/native/src/BpfCompatTest.cpp
+++ b/tests/cts/net/native/src/BpfCompatTest.cpp
@@ -27,35 +27,32 @@
 
 using namespace android::bpf;
 
-void doBpfStructSizeTest(const char *elfPath) {
+void doBpfStructSizeTest(const char *elfPath, unsigned mapSz, unsigned progSz) {
   std::ifstream elfFile(elfPath, std::ios::in | std::ios::binary);
   ASSERT_TRUE(elfFile.is_open());
 
-  if (android::modules::sdklevel::IsAtLeastU()) {
-    EXPECT_EQ(120, readSectionUint("size_of_bpf_map_def", elfFile, 0));
-    EXPECT_EQ(92, readSectionUint("size_of_bpf_prog_def", elfFile, 0));
-  } else if (android::modules::sdklevel::IsAtLeastT()) {
-    EXPECT_EQ(116, readSectionUint("size_of_bpf_map_def", elfFile, 0));
-    EXPECT_EQ(92, readSectionUint("size_of_bpf_prog_def", elfFile, 0));
-  } else {
-    EXPECT_EQ(48, readSectionUint("size_of_bpf_map_def", elfFile, 0));
-    EXPECT_EQ(28, readSectionUint("size_of_bpf_prog_def", elfFile, 0));
-  }
-}
-
-TEST(BpfTest, bpfStructSizeTestPreT) {
-  if (android::modules::sdklevel::IsAtLeastT()) GTEST_SKIP() << "T+ device.";
-  doBpfStructSizeTest("/system/etc/bpf/netd.o");
-  doBpfStructSizeTest("/system/etc/bpf/clatd.o");
+  EXPECT_EQ(mapSz, readSectionUint("size_of_bpf_map_def", elfFile, 0));
+  EXPECT_EQ(progSz, readSectionUint("size_of_bpf_prog_def", elfFile, 0));
 }
 
 TEST(BpfTest, bpfStructSizeTest) {
-  if (android::modules::sdklevel::IsAtLeastU()) {
-      doBpfStructSizeTest("/system/etc/bpf/gpuMem.o");
-      doBpfStructSizeTest("/system/etc/bpf/timeInState.o");
+  if (android::modules::sdklevel::IsAtLeastV()) {
+    // Due to V+ using mainline netbpfload, there is no longer a need to
+    // enforce consistency between platform and mainline bpf .o files.
+    GTEST_SKIP() << "V+ device.";
+  } else if (android::modules::sdklevel::IsAtLeastU()) {
+    doBpfStructSizeTest("/system/etc/bpf/gpuMem.o", 120, 92);
+    doBpfStructSizeTest("/system/etc/bpf/timeInState.o", 120, 92);
+  } else if (android::modules::sdklevel::IsAtLeastT()) {
+    doBpfStructSizeTest("/system/etc/bpf/gpu_mem.o", 116, 92);
+    doBpfStructSizeTest("/system/etc/bpf/time_in_state.o", 116, 92);
+  } else if (android::modules::sdklevel::IsAtLeastS()) {
+    // These files were moved to mainline in Android T
+    doBpfStructSizeTest("/system/etc/bpf/netd.o", 48, 28);
+    doBpfStructSizeTest("/system/etc/bpf/clatd.o", 48, 28);
   } else {
-      doBpfStructSizeTest("/system/etc/bpf/gpu_mem.o");
-      doBpfStructSizeTest("/system/etc/bpf/time_in_state.o");
+    // There is no mainline bpf code before S.
+    GTEST_SKIP() << "R- device.";
   }
 }
 
diff --git a/tests/cts/net/src/android/net/cts/ApfIntegrationTest.kt b/tests/cts/net/src/android/net/cts/ApfIntegrationTest.kt
index 1a535b4..a679498 100644
--- a/tests/cts/net/src/android/net/cts/ApfIntegrationTest.kt
+++ b/tests/cts/net/src/android/net/cts/ApfIntegrationTest.kt
@@ -26,9 +26,9 @@
 import android.net.NetworkCapabilities
 import android.net.NetworkRequest
 import android.net.apf.ApfCapabilities
-import android.net.apf.ApfConstant.ETH_ETHERTYPE_OFFSET
-import android.net.apf.ApfConstant.ICMP6_TYPE_OFFSET
-import android.net.apf.ApfConstant.IPV6_NEXT_HEADER_OFFSET
+import android.net.apf.ApfConstants.ETH_ETHERTYPE_OFFSET
+import android.net.apf.ApfConstants.ICMP6_TYPE_OFFSET
+import android.net.apf.ApfConstants.IPV6_NEXT_HEADER_OFFSET
 import android.net.apf.ApfV4Generator
 import android.net.apf.BaseApfGenerator
 import android.net.apf.BaseApfGenerator.MemorySlot
@@ -313,18 +313,20 @@
         // - [GMS-VSR-5.3.12-004] MUST indicate at least 1024 bytes of usable memory from calls to
         //   the getApfPacketFilterCapabilities HAL method.
         // TODO: check whether above text should be changed "34 or higher"
-        // This should assert apfVersionSupported >= 4 as per the VSR requirements, but there are
-        // currently no tests for APFv6 and there cannot be a valid implementation as the
-        // interpreter has yet to be finalized.
-        assertThat(caps.apfVersionSupported).isEqualTo(4)
+        assertThat(caps.apfVersionSupported).isAtLeast(4)
         assertThat(caps.maximumApfProgramSize).isAtLeast(1024)
 
+        if (caps.apfVersionSupported > 4) {
+            assertThat(caps.maximumApfProgramSize).isAtLeast(2048)
+            assertThat(caps.apfVersionSupported).isEqualTo(6000)  // v6.0000
+        }
+
         // DEVICEs launching with Android 15 (AOSP experimental) or higher with CHIPSETs that set
         // ro.board.first_api_level or ro.board.api_level to 202404 or higher:
-        // - [GMS-VSR-5.3.12-009] MUST indicate at least 2000 bytes of usable memory from calls to
+        // - [GMS-VSR-5.3.12-009] MUST indicate at least 2048 bytes of usable memory from calls to
         //   the getApfPacketFilterCapabilities HAL method.
         if (getVsrApiLevel() >= 202404) {
-            assertThat(caps.maximumApfProgramSize).isAtLeast(2000)
+            assertThat(caps.maximumApfProgramSize).isAtLeast(2048)
         }
     }
 
@@ -419,14 +421,15 @@
         packetReader.expectPingDropped()
     }
 
+    fun clearApfMemory() = installProgram(ByteArray(caps.maximumApfProgramSize))
+
     // APF integration is mostly broken before V
     @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
     @Test
     fun testPrefilledMemorySlotsV4() {
         // Test v4 memory slots on both v4 and v6 interpreters.
         assumeApfVersionSupportAtLeast(4)
-        // Clear the entire memory before starting this test
-        installProgram(ByteArray(caps.maximumApfProgramSize))
+        clearApfMemory()
         val gen = ApfV4Generator(4)
 
         // If not ICMPv6 Echo Reply -> PASS
@@ -457,8 +460,7 @@
         packetReader.expectPingReply()
 
         val readResult = readProgram()
-        val buffer = ByteBuffer.wrap(readResult)
-        buffer.position(counterRegion)
+        val buffer = ByteBuffer.wrap(readResult, counterRegion, 20 /* length */)
         expect.withMessage("PROGRAM_SIZE").that(buffer.getInt()).isEqualTo(program.size)
         expect.withMessage("RAM_LEN").that(buffer.getInt()).isEqualTo(caps.maximumApfProgramSize)
         expect.withMessage("IPV4_HEADER_SIZE").that(buffer.getInt()).isEqualTo(0)
diff --git a/tests/cts/net/src/android/net/cts/IpSecManagerTest.java b/tests/cts/net/src/android/net/cts/IpSecManagerTest.java
index b703f77..b5f43d3 100644
--- a/tests/cts/net/src/android/net/cts/IpSecManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/IpSecManagerTest.java
@@ -69,9 +69,11 @@
 import android.net.IpSecManager.SecurityParameterIndex;
 import android.net.IpSecManager.UdpEncapsulationSocket;
 import android.net.IpSecTransform;
+import android.net.IpSecTransformState;
 import android.net.NetworkUtils;
 import android.net.TrafficStats;
 import android.os.Build;
+import android.os.OutcomeReceiver;
 import android.platform.test.annotations.AppModeFull;
 import android.system.ErrnoException;
 import android.system.Os;
@@ -101,6 +103,9 @@
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
 
 @ConnectivityModuleTest
 @RunWith(AndroidJUnit4.class)
@@ -1654,4 +1659,37 @@
                     newReplayBitmap(expectedPacketCount));
         }
     }
+
+    @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    @Test
+    public void testRequestIpSecTransformStateOnClosedTransform() throws Exception {
+        assumeRequestIpSecTransformStateSupported();
+
+        final InetAddress localAddr = InetAddresses.parseNumericAddress(IPV6_LOOPBACK);
+        final CompletableFuture<RuntimeException> futureError = new CompletableFuture<>();
+
+        try (SecurityParameterIndex spi = mISM.allocateSecurityParameterIndex(localAddr);
+                IpSecTransform transform =
+                        buildTransportModeTransform(spi, localAddr, null /* encapSocket*/)) {
+            transform.close();
+
+            transform.requestIpSecTransformState(
+                    Executors.newSingleThreadExecutor(),
+                    new OutcomeReceiver<IpSecTransformState, RuntimeException>() {
+                        @Override
+                        public void onResult(IpSecTransformState state) {
+                            fail("Expect to fail but received a state");
+                        }
+
+                        @Override
+                        public void onError(RuntimeException error) {
+                            futureError.complete(error);
+                        }
+                    });
+
+            assertTrue(
+                    futureError.get(SOCK_TIMEOUT, TimeUnit.MILLISECONDS)
+                            instanceof IllegalStateException);
+        }
+    }
 }
diff --git a/tests/cts/net/src/android/net/cts/NetworkRequestTest.java b/tests/cts/net/src/android/net/cts/NetworkRequestTest.java
index 6ec4e62..5b53839 100644
--- a/tests/cts/net/src/android/net/cts/NetworkRequestTest.java
+++ b/tests/cts/net/src/android/net/cts/NetworkRequestTest.java
@@ -68,6 +68,7 @@
 import com.android.testutils.DevSdkIgnoreRule;
 import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
 
+import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -511,7 +512,7 @@
         assertArrayEquals(netCapabilities, nr.getCapabilities());
     }
 
-    @Test @IgnoreUpTo(VANILLA_ICE_CREAM)
+    @Test @IgnoreUpTo(VANILLA_ICE_CREAM) @Ignore("b/338200742")
     public void testDefaultCapabilities() {
         final NetworkRequest defaultNR = new NetworkRequest.Builder().build();
         assertTrue(defaultNR.hasForbiddenCapability(NET_CAPABILITY_LOCAL_NETWORK));
diff --git a/tests/cts/net/util/java/android/net/cts/util/CtsNetUtils.java b/tests/cts/net/util/java/android/net/cts/util/CtsNetUtils.java
index 670889f..0dd2a23 100644
--- a/tests/cts/net/util/java/android/net/cts/util/CtsNetUtils.java
+++ b/tests/cts/net/util/java/android/net/cts/util/CtsNetUtils.java
@@ -93,7 +93,7 @@
     private static final int SOCKET_TIMEOUT_MS = 10_000;
     private static final int PRIVATE_DNS_PROBE_MS = 1_000;
 
-    private static final int PRIVATE_DNS_SETTING_TIMEOUT_MS = 10_000;
+    private static final int PRIVATE_DNS_SETTING_TIMEOUT_MS = 30_000;
     private static final int CONNECTIVITY_CHANGE_TIMEOUT_SECS = 30;
 
     private static final String PRIVATE_DNS_MODE_OPPORTUNISTIC = "opportunistic";
diff --git a/tests/unit/java/com/android/server/BpfNetMapsTest.java b/tests/unit/java/com/android/server/BpfNetMapsTest.java
index fa79795..cbc060a 100644
--- a/tests/unit/java/com/android/server/BpfNetMapsTest.java
+++ b/tests/unit/java/com/android/server/BpfNetMapsTest.java
@@ -17,6 +17,7 @@
 package com.android.server;
 
 import static android.net.BpfNetMapsConstants.ALLOW_CHAINS;
+import static android.net.BpfNetMapsConstants.BACKGROUND_MATCH;
 import static android.net.BpfNetMapsConstants.CURRENT_STATS_MAP_CONFIGURATION_KEY;
 import static android.net.BpfNetMapsConstants.DATA_SAVER_ENABLED_KEY;
 import static android.net.BpfNetMapsConstants.DATA_SAVER_DISABLED;
@@ -37,6 +38,14 @@
 import static android.net.BpfNetMapsConstants.RESTRICTED_MATCH;
 import static android.net.BpfNetMapsConstants.STANDBY_MATCH;
 import static android.net.BpfNetMapsConstants.UID_RULES_CONFIGURATION_KEY;
+import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_ADMIN_DISABLED;
+import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_DATA_SAVER;
+import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_USER_RESTRICTED;
+import static android.net.ConnectivityManager.BLOCKED_REASON_APP_STANDBY;
+import static android.net.ConnectivityManager.BLOCKED_REASON_BATTERY_SAVER;
+import static android.net.ConnectivityManager.BLOCKED_REASON_DOZE;
+import static android.net.ConnectivityManager.BLOCKED_REASON_NONE;
+import static android.net.ConnectivityManager.BLOCKED_REASON_OEM_DENY;
 import static android.net.ConnectivityManager.FIREWALL_CHAIN_DOZABLE;
 import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY;
 import static android.net.ConnectivityManager.FIREWALL_CHAIN_METERED_ALLOW;
@@ -839,6 +848,21 @@
     }
 
     @Test
+    @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+    public void testGetNetPermFoUid() throws Exception {
+        mUidPermissionMap.deleteEntry(new S32(TEST_UID));
+        assertEquals(PERMISSION_INTERNET, mBpfNetMaps.getNetPermForUid(TEST_UID));
+
+        mUidPermissionMap.updateEntry(new S32(TEST_UID), new U8((short) PERMISSION_NONE));
+        assertEquals(PERMISSION_NONE, mBpfNetMaps.getNetPermForUid(TEST_UID));
+
+        mUidPermissionMap.updateEntry(new S32(TEST_UID),
+                new U8((short) (PERMISSION_INTERNET | PERMISSION_UPDATE_DEVICE_STATS)));
+        assertEquals(PERMISSION_INTERNET | PERMISSION_UPDATE_DEVICE_STATS,
+                mBpfNetMaps.getNetPermForUid(TEST_UID));
+    }
+
+    @Test
     @IgnoreUpTo(Build.VERSION_CODES.S_V2)
     public void testSwapActiveStatsMap() throws Exception {
         mConfigurationMap.updateEntry(
@@ -1150,4 +1174,138 @@
         assertDumpContains(dump, TEST_V6_ADDRESS.getHostAddress());
         assertDumpContains(dump, TEST_IF_INDEX + "(" + TEST_IF_NAME + ")");
     }
+
+    private void doTestGetUidNetworkingBlockedReasons(
+            final long configurationMatches,
+            final long uidRules,
+            final short dataSaverStatus,
+            final int expectedBlockedReasons
+    ) throws Exception {
+        mConfigurationMap.updateEntry(UID_RULES_CONFIGURATION_KEY, new U32(configurationMatches));
+        mUidOwnerMap.updateEntry(new S32(TEST_UID), new UidOwnerValue(NULL_IIF, uidRules));
+        mDataSaverEnabledMap.updateEntry(DATA_SAVER_ENABLED_KEY, new U8(dataSaverStatus));
+
+        assertEquals(expectedBlockedReasons, mBpfNetMaps.getUidNetworkingBlockedReasons(TEST_UID));
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+    public void testGetUidNetworkingBlockedReasons() throws Exception {
+        doTestGetUidNetworkingBlockedReasons(
+                NO_MATCH,
+                NO_MATCH,
+                DATA_SAVER_DISABLED,
+                BLOCKED_REASON_NONE
+        );
+        doTestGetUidNetworkingBlockedReasons(
+                DOZABLE_MATCH,
+                NO_MATCH,
+                DATA_SAVER_DISABLED,
+                BLOCKED_REASON_DOZE
+        );
+        doTestGetUidNetworkingBlockedReasons(
+                DOZABLE_MATCH | POWERSAVE_MATCH | STANDBY_MATCH,
+                DOZABLE_MATCH | STANDBY_MATCH,
+                DATA_SAVER_DISABLED,
+                BLOCKED_REASON_BATTERY_SAVER | BLOCKED_REASON_APP_STANDBY
+        );
+        doTestGetUidNetworkingBlockedReasons(
+                OEM_DENY_1_MATCH | OEM_DENY_2_MATCH | OEM_DENY_3_MATCH,
+                OEM_DENY_1_MATCH | OEM_DENY_3_MATCH,
+                DATA_SAVER_DISABLED,
+                BLOCKED_REASON_OEM_DENY
+        );
+        doTestGetUidNetworkingBlockedReasons(
+                DOZABLE_MATCH,
+                DOZABLE_MATCH | BACKGROUND_MATCH | STANDBY_MATCH,
+                DATA_SAVER_DISABLED,
+                BLOCKED_REASON_NONE
+        );
+
+        // Note that HAPPY_BOX and PENALTY_BOX are not disabled by configuration map
+        doTestGetUidNetworkingBlockedReasons(
+                NO_MATCH,
+                PENALTY_BOX_USER_MATCH,
+                DATA_SAVER_DISABLED,
+                BLOCKED_METERED_REASON_USER_RESTRICTED
+        );
+        doTestGetUidNetworkingBlockedReasons(
+                NO_MATCH,
+                PENALTY_BOX_ADMIN_MATCH,
+                DATA_SAVER_ENABLED,
+                BLOCKED_METERED_REASON_ADMIN_DISABLED | BLOCKED_METERED_REASON_DATA_SAVER
+        );
+        doTestGetUidNetworkingBlockedReasons(
+                NO_MATCH,
+                PENALTY_BOX_USER_MATCH | PENALTY_BOX_ADMIN_MATCH | HAPPY_BOX_MATCH,
+                DATA_SAVER_ENABLED,
+                BLOCKED_METERED_REASON_USER_RESTRICTED | BLOCKED_METERED_REASON_ADMIN_DISABLED
+        );
+        doTestGetUidNetworkingBlockedReasons(
+                STANDBY_MATCH,
+                STANDBY_MATCH | PENALTY_BOX_USER_MATCH | HAPPY_BOX_MATCH,
+                DATA_SAVER_ENABLED,
+                BLOCKED_REASON_APP_STANDBY | BLOCKED_METERED_REASON_USER_RESTRICTED
+        );
+    }
+
+    private void doTestIsUidRestrictedOnMeteredNetworks(
+            final long enabledMatches,
+            final long uidRules,
+            final short dataSaver,
+            final boolean expectedRestricted
+    ) throws Exception {
+        mConfigurationMap.updateEntry(UID_RULES_CONFIGURATION_KEY, new U32(enabledMatches));
+        mUidOwnerMap.updateEntry(new S32(TEST_UID), new UidOwnerValue(NULL_IIF, uidRules));
+        mDataSaverEnabledMap.updateEntry(DATA_SAVER_ENABLED_KEY, new U8(dataSaver));
+
+        assertEquals(expectedRestricted, mBpfNetMaps.isUidRestrictedOnMeteredNetworks(TEST_UID));
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+    public void testIsUidRestrictedOnMeteredNetworks() throws Exception {
+        doTestIsUidRestrictedOnMeteredNetworks(
+                NO_MATCH,
+                NO_MATCH,
+                DATA_SAVER_DISABLED,
+                false /* expectRestricted */
+        );
+        doTestIsUidRestrictedOnMeteredNetworks(
+                DOZABLE_MATCH | POWERSAVE_MATCH | STANDBY_MATCH,
+                DOZABLE_MATCH | STANDBY_MATCH ,
+                DATA_SAVER_DISABLED,
+                false /* expectRestricted */
+        );
+        doTestIsUidRestrictedOnMeteredNetworks(
+                NO_MATCH,
+                PENALTY_BOX_USER_MATCH,
+                DATA_SAVER_DISABLED,
+                true /* expectRestricted */
+        );
+        doTestIsUidRestrictedOnMeteredNetworks(
+                NO_MATCH,
+                PENALTY_BOX_ADMIN_MATCH,
+                DATA_SAVER_DISABLED,
+                true /* expectRestricted */
+        );
+        doTestIsUidRestrictedOnMeteredNetworks(
+                NO_MATCH,
+                PENALTY_BOX_USER_MATCH | PENALTY_BOX_ADMIN_MATCH | HAPPY_BOX_MATCH,
+                DATA_SAVER_DISABLED,
+                true /* expectRestricted */
+        );
+        doTestIsUidRestrictedOnMeteredNetworks(
+                NO_MATCH,
+                NO_MATCH,
+                DATA_SAVER_ENABLED,
+                true /* expectRestricted */
+        );
+        doTestIsUidRestrictedOnMeteredNetworks(
+                NO_MATCH,
+                HAPPY_BOX_MATCH,
+                DATA_SAVER_ENABLED,
+                false /* expectRestricted */
+        );
+    }
 }
diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
index aee40c8..b6cb09b 100755
--- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
@@ -87,6 +87,7 @@
 import static android.net.ConnectivitySettingsManager.PRIVATE_DNS_MODE_OFF;
 import static android.net.ConnectivitySettingsManager.PRIVATE_DNS_MODE_OPPORTUNISTIC;
 import static android.net.ConnectivitySettingsManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME;
+import static android.net.INetd.PERMISSION_INTERNET;
 import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_DNS;
 import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_FALLBACK;
 import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_HTTP;
@@ -151,6 +152,7 @@
 import static android.net.OemNetworkPreferences.OEM_NETWORK_PREFERENCE_UNINITIALIZED;
 import static android.net.Proxy.PROXY_CHANGE_ACTION;
 import static android.net.RouteInfo.RTN_UNREACHABLE;
+import static android.net.connectivity.ConnectivityCompatChanges.NETWORK_BLOCKED_WITHOUT_INTERNET_PERMISSION;
 import static android.net.resolv.aidl.IDnsResolverUnsolicitedEventListener.PREFIX_OPERATION_ADDED;
 import static android.net.resolv.aidl.IDnsResolverUnsolicitedEventListener.PREFIX_OPERATION_REMOVED;
 import static android.net.resolv.aidl.IDnsResolverUnsolicitedEventListener.VALIDATION_RESULT_FAILURE;
@@ -803,8 +805,10 @@
             // This relies on all contexts for a given user returning the same UM mock
             final DevicePolicyManager dpmMock = createContextAsUser(userHandle, 0 /* flags */)
                     .getSystemService(DevicePolicyManager.class);
-            doReturn(value).when(dpmMock).getDeviceOwner();
-            doReturn(value).when(mDevicePolicyManager).getDeviceOwner();
+            ComponentName componentName = value == null
+                    ? null : new ComponentName(value, "deviceOwnerClass");
+            doReturn(componentName).when(dpmMock).getDeviceOwnerComponentOnAnyUser();
+            doReturn(componentName).when(mDevicePolicyManager).getDeviceOwnerComponentOnAnyUser();
         }
 
         @Override
@@ -1725,6 +1729,8 @@
     private void mockUidNetworkingBlocked() {
         doAnswer(i -> isUidBlocked(mBlockedReasons, i.getArgument(1))
         ).when(mNetworkPolicyManager).isUidNetworkingBlocked(anyInt(), anyBoolean());
+        doAnswer(i -> isUidBlocked(mBlockedReasons, i.getArgument(1))
+        ).when(mBpfNetMaps).isUidNetworkingBlocked(anyInt(), anyBoolean());
     }
 
     private boolean isUidBlocked(int blockedReasons, boolean meteredNetwork) {
@@ -1941,6 +1947,10 @@
         setCaptivePortalMode(ConnectivitySettingsManager.CAPTIVE_PORTAL_MODE_PROMPT);
         setAlwaysOnNetworks(false);
         setPrivateDnsSettings(PRIVATE_DNS_MODE_OFF, "ignored.example.com");
+
+        mDeps.setChangeIdEnabled(
+                true, NETWORK_BLOCKED_WITHOUT_INTERNET_PERMISSION, Process.myUid());
+        doReturn(PERMISSION_INTERNET).when(mBpfNetMaps).getNetPermForUid(anyInt());
         // Note : Please do not add any new instrumentation here. If you need new instrumentation,
         // please add it in CSTest and use subclasses of CSTest instead of adding more
         // tools in ConnectivityServiceTest.
diff --git a/tests/unit/java/com/android/server/connectivity/ClatCoordinatorTest.java b/tests/unit/java/com/android/server/connectivity/ClatCoordinatorTest.java
index da7fda3..72dde7f 100644
--- a/tests/unit/java/com/android/server/connectivity/ClatCoordinatorTest.java
+++ b/tests/unit/java/com/android/server/connectivity/ClatCoordinatorTest.java
@@ -38,6 +38,7 @@
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.eq;
 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.verify;
@@ -418,21 +419,6 @@
         inOrder.verify(mDeps).generateIpv6Address(eq(BASE_IFACE),
                 eq(XLAT_LOCAL_IPV4ADDR_STRING), eq(NAT64_PREFIX_STRING), eq(MARK));
 
-        // Open, configure and bring up the tun interface.
-        inOrder.verify(mDeps).createTunInterface(eq(STACKED_IFACE));
-        inOrder.verify(mDeps).adoptFd(eq(TUN_FD));
-        inOrder.verify(mDeps).getInterfaceIndex(eq(STACKED_IFACE));
-        inOrder.verify(mNetd).interfaceSetEnableIPv6(eq(STACKED_IFACE), eq(false /* enable */));
-        inOrder.verify(mDeps).detectMtu(eq(NAT64_PREFIX_STRING), eq(GOOGLE_DNS_4), eq(MARK));
-        inOrder.verify(mNetd).interfaceSetMtu(eq(STACKED_IFACE),
-                eq(1472 /* ETHER_MTU(1500) - MTU_DELTA(28) */));
-        inOrder.verify(mNetd).interfaceSetCfg(argThat(cfg ->
-                STACKED_IFACE.equals(cfg.ifName)
-                && XLAT_LOCAL_IPV4ADDR_STRING.equals(cfg.ipv4Addr)
-                && (32 == cfg.prefixLength)
-                && "".equals(cfg.hwAddr)
-                && assertContainsFlag(cfg.flags, IF_STATE_UP)));
-
         // Open and configure 464xlat read/write sockets.
         inOrder.verify(mDeps).openPacketSocket();
         inOrder.verify(mDeps).adoptFd(eq(PACKET_SOCK_FD));
@@ -449,6 +435,21 @@
                 argThat(fd -> Objects.equals(PACKET_SOCK_PFD.getFileDescriptor(), fd)),
                 eq(XLAT_LOCAL_IPV6ADDR_STRING), eq(BASE_IFINDEX));
 
+        // Open, configure and bring up the tun interface.
+        inOrder.verify(mDeps).createTunInterface(eq(STACKED_IFACE));
+        inOrder.verify(mDeps).adoptFd(eq(TUN_FD));
+        inOrder.verify(mDeps).getInterfaceIndex(eq(STACKED_IFACE));
+        inOrder.verify(mNetd).interfaceSetEnableIPv6(eq(STACKED_IFACE), eq(false /* enable */));
+        inOrder.verify(mDeps).detectMtu(eq(NAT64_PREFIX_STRING), eq(GOOGLE_DNS_4), eq(MARK));
+        inOrder.verify(mNetd).interfaceSetMtu(eq(STACKED_IFACE),
+                eq(1472 /* ETHER_MTU(1500) - MTU_DELTA(28) */));
+        inOrder.verify(mNetd).interfaceSetCfg(argThat(cfg ->
+                STACKED_IFACE.equals(cfg.ifName)
+                        && XLAT_LOCAL_IPV4ADDR_STRING.equals(cfg.ipv4Addr)
+                        && (32 == cfg.prefixLength)
+                        && "".equals(cfg.hwAddr)
+                        && assertContainsFlag(cfg.flags, IF_STATE_UP)));
+
         // Start clatd.
         inOrder.verify(mDeps).startClatd(
                 argThat(fd -> Objects.equals(TUN_PFD.getFileDescriptor(), fd)),
@@ -630,9 +631,13 @@
             public int createTunInterface(@NonNull String tuniface) throws IOException {
                 throw new IOException();
             }
+            @Override
+            public IBpfMap<CookieTagMapKey, CookieTagMapValue> getBpfCookieTagMap() {
+                return  mock(IBpfMap.class);
+            }
         }
         checkNotStartClat(new FailureDependencies(), false /* needToCloseTunFd */,
-                false /* needToClosePacketSockFd */, false /* needToCloseRawSockFd */);
+                true /* needToClosePacketSockFd */, true /* needToCloseRawSockFd */);
     }
 
     @Test
@@ -643,9 +648,14 @@
                     throws IOException {
                 throw new IOException();
             }
+
+            @Override
+            public IBpfMap<CookieTagMapKey, CookieTagMapValue> getBpfCookieTagMap() {
+                return  mock(IBpfMap.class);
+            }
         }
         checkNotStartClat(new FailureDependencies(), true /* needToCloseTunFd */,
-                false /* needToClosePacketSockFd */, false /* needToCloseRawSockFd */);
+                true /* needToClosePacketSockFd */, true /* needToCloseRawSockFd */);
     }
 
     @Test
@@ -656,7 +666,7 @@
                 throw new IOException();
             }
         }
-        checkNotStartClat(new FailureDependencies(), true /* needToCloseTunFd */,
+        checkNotStartClat(new FailureDependencies(), false /* needToCloseTunFd */,
                 false /* needToClosePacketSockFd */, false /* needToCloseRawSockFd */);
     }
 
@@ -668,7 +678,7 @@
                 throw new IOException();
             }
         }
-        checkNotStartClat(new FailureDependencies(), true /* needToCloseTunFd */,
+        checkNotStartClat(new FailureDependencies(), false /* needToCloseTunFd */,
                 true /* needToClosePacketSockFd */, false /* needToCloseRawSockFd */);
     }
 
@@ -681,7 +691,7 @@
                 throw new IOException();
             }
         }
-        checkNotStartClat(new FailureDependencies(), true /* needToCloseTunFd */,
+        checkNotStartClat(new FailureDependencies(), false /* needToCloseTunFd */,
                 true /* needToClosePacketSockFd */, true /* needToCloseRawSockFd */);
     }
 
@@ -694,7 +704,7 @@
                 throw new IOException();
             }
         }
-        checkNotStartClat(new FailureDependencies(), true /* needToCloseTunFd */,
+        checkNotStartClat(new FailureDependencies(), false /* needToCloseTunFd */,
                 true /* needToClosePacketSockFd */, true /* needToCloseRawSockFd */);
     }
 
@@ -721,7 +731,7 @@
                 throw new IOException();
             }
         }
-        checkNotStartClat(new FailureDependencies(), true /* needToCloseTunFd */,
+        checkNotStartClat(new FailureDependencies(), false /* needToCloseTunFd */,
                 true /* needToClosePacketSockFd */, true /* needToCloseRawSockFd */);
     }
 
@@ -733,7 +743,7 @@
                 return null;
             }
         }
-        checkNotStartClat(new FailureDependencies(), true /* needToCloseTunFd */,
+        checkNotStartClat(new FailureDependencies(), false /* needToCloseTunFd */,
                 true /* needToClosePacketSockFd */, true /* needToCloseRawSockFd */);
     }
 }
diff --git a/tests/unit/java/com/android/server/connectivity/DnsManagerTest.java b/tests/unit/java/com/android/server/connectivity/DnsManagerTest.java
index 44512bb..ea3d2dd 100644
--- a/tests/unit/java/com/android/server/connectivity/DnsManagerTest.java
+++ b/tests/unit/java/com/android/server/connectivity/DnsManagerTest.java
@@ -29,8 +29,6 @@
 import static android.net.resolv.aidl.IDnsResolverUnsolicitedEventListener.VALIDATION_RESULT_FAILURE;
 import static android.net.resolv.aidl.IDnsResolverUnsolicitedEventListener.VALIDATION_RESULT_SUCCESS;
 
-import static com.android.testutils.MiscAsserts.assertContainsExactly;
-import static com.android.testutils.MiscAsserts.assertContainsStringsExactly;
 import static com.android.testutils.MiscAsserts.assertFieldCountEquals;
 
 import static org.junit.Assert.assertEquals;
@@ -38,12 +36,12 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
 import android.net.ConnectivitySettingsManager;
@@ -74,7 +72,6 @@
 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;
 
@@ -122,29 +119,6 @@
         assertFieldCountEquals(3, ResolverOptionsParcel.class);
     }
 
-    private void assertResolverParamsEquals(@NonNull ResolverParamsParcel actual,
-            @NonNull ResolverParamsParcel expected) {
-        assertEquals(actual.netId, expected.netId);
-        assertEquals(actual.sampleValiditySeconds, expected.sampleValiditySeconds);
-        assertEquals(actual.successThreshold, expected.successThreshold);
-        assertEquals(actual.minSamples, expected.minSamples);
-        assertEquals(actual.maxSamples, expected.maxSamples);
-        assertEquals(actual.baseTimeoutMsec, expected.baseTimeoutMsec);
-        assertEquals(actual.retryCount, expected.retryCount);
-        assertContainsStringsExactly(actual.servers, expected.servers);
-        assertContainsStringsExactly(actual.domains, expected.domains);
-        assertEquals(actual.tlsName, expected.tlsName);
-        assertContainsStringsExactly(actual.tlsServers, expected.tlsServers);
-        assertContainsStringsExactly(actual.tlsFingerprints, expected.tlsFingerprints);
-        assertEquals(actual.caCertificate, expected.caCertificate);
-        assertEquals(actual.tlsConnectTimeoutMs, expected.tlsConnectTimeoutMs);
-        assertResolverOptionsEquals(actual.resolverOptions, expected.resolverOptions);
-        assertContainsExactly(actual.transportTypes, expected.transportTypes);
-        assertEquals(actual.meteredNetwork, expected.meteredNetwork);
-        assertEquals(actual.dohParams, expected.dohParams);
-        assertFieldCountEquals(18, ResolverParamsParcel.class);
-    }
-
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
@@ -365,11 +339,6 @@
         mDnsManager.noteDnsServersForNetwork(TEST_NETID, lp);
         mDnsManager.flushVmDnsCache();
 
-        final ArgumentCaptor<ResolverParamsParcel> resolverParamsParcelCaptor =
-                ArgumentCaptor.forClass(ResolverParamsParcel.class);
-        verify(mMockDnsResolver, times(1)).setResolverConfiguration(
-                resolverParamsParcelCaptor.capture());
-        final ResolverParamsParcel actualParams = resolverParamsParcelCaptor.getValue();
         final ResolverParamsParcel expectedParams = new ResolverParamsParcel();
         expectedParams.netId = TEST_NETID;
         expectedParams.sampleValiditySeconds = TEST_DEFAULT_SAMPLE_VALIDITY_SECONDS;
@@ -384,7 +353,8 @@
         expectedParams.resolverOptions = null;
         expectedParams.meteredNetwork = true;
         expectedParams.dohParams = null;
-        assertResolverParamsEquals(actualParams, expectedParams);
+        expectedParams.interfaceNames = new String[]{TEST_IFACENAME};
+        verify(mMockDnsResolver, times(1)).setResolverConfiguration(eq(expectedParams));
     }
 
     @Test
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 d735dc6..2cb97c9 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsRecordRepositoryTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsRecordRepositoryTest.kt
@@ -1625,6 +1625,44 @@
     }
 
     @Test
+    fun testGetConflictingServices_multipleRegistrationsForHostKey_noConflict() {
+        val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
+
+        repository.addServiceAndFinishProbing(TEST_SERVICE_ID_1, NsdServiceInfo().apply {
+            hostname = "MyHost"
+            hostAddresses = listOf(
+                parseNumericAddress("2001:db8::1"),
+                parseNumericAddress("2001:db8::2"))
+            publicKey = TEST_PUBLIC_KEY
+        })
+        repository.addService(TEST_SERVICE_ID_2, NsdServiceInfo().apply {
+            serviceType = "_testservice._tcp"
+            serviceName = "MyTestService"
+            port = TEST_PORT
+            hostname = "MyHost"
+            publicKey = TEST_PUBLIC_KEY
+        }, null /* ttl */)
+
+        // Although there's a KEY RR in the second registration being probed, it shouldn't conflict
+        // with an address record which is from a probed registration in the repository.
+        val otherTtlMillis = 1234L
+        val packet = MdnsPacket(
+            0 /* flags */,
+            emptyList() /* questions */,
+            listOf(
+                MdnsInetAddressRecord(
+                    arrayOf("MyHost", "local"),
+                    0L /* receiptTimeMillis */, true /* cacheFlush */,
+                    otherTtlMillis,
+                    parseNumericAddress("2001:db8::1"))
+            ) /* answers */,
+            emptyList() /* authorityRecords */,
+            emptyList() /* additionalRecords */)
+
+        assertEquals(mapOf(), repository.getConflictingServices(packet))
+    }
+
+    @Test
     fun testGetConflictingServices_IdenticalService() {
         val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
         repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1, null /* ttl */)
diff --git a/tests/unit/java/com/android/server/connectivityservice/CSActiveNetworkInfoTest.kt b/tests/unit/java/com/android/server/connectivityservice/CSActiveNetworkInfoTest.kt
new file mode 100644
index 0000000..360a298
--- /dev/null
+++ b/tests/unit/java/com/android/server/connectivityservice/CSActiveNetworkInfoTest.kt
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2024 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
+
+import android.net.INetd.PERMISSION_INTERNET
+import android.net.INetd.PERMISSION_NONE
+import android.net.NetworkCapabilities
+import android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET
+import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED
+import android.net.NetworkCapabilities.TRANSPORT_WIFI
+import android.net.NetworkInfo.DetailedState.BLOCKED
+import android.net.NetworkInfo.DetailedState.CONNECTED
+import android.net.connectivity.ConnectivityCompatChanges.NETWORK_BLOCKED_WITHOUT_INTERNET_PERMISSION
+import android.os.Build
+import androidx.test.filters.SmallTest
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
+import com.android.testutils.DevSdkIgnoreRunner
+import kotlin.test.assertEquals
+import kotlin.test.assertNotNull
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mockito.doReturn
+
+private fun nc() = NetworkCapabilities.Builder()
+        .addTransportType(TRANSPORT_WIFI)
+        .addCapability(NET_CAPABILITY_INTERNET)
+        .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
+        .build()
+
+@RunWith(DevSdkIgnoreRunner::class)
+@SmallTest
+@IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+class CSActiveNetworkInfoTest : CSTest() {
+
+    fun doTestGetActiveNetworkInfo(
+            changeEnabled: Boolean,
+            permissions: Int,
+            expectBlocked: Boolean
+    ) {
+        deps.setChangeIdEnabled(changeEnabled, NETWORK_BLOCKED_WITHOUT_INTERNET_PERMISSION)
+        doReturn(permissions).`when`(bpfNetMaps).getNetPermForUid(anyInt())
+
+        val agent = Agent(nc = nc())
+        agent.connect()
+
+        val networkInfo = cm.activeNetworkInfo
+        assertNotNull(networkInfo)
+        if (expectBlocked) {
+            assertEquals(BLOCKED, networkInfo.detailedState)
+        } else {
+            assertEquals(CONNECTED, networkInfo.detailedState)
+        }
+        assertEquals(expectBlocked, cm.activeNetwork == null)
+        agent.disconnect()
+    }
+
+    @Test
+    fun testGetActiveNetworkInfo() {
+        doReturn(true).`when`(bpfNetMaps).isUidNetworkingBlocked(anyInt(), anyBoolean())
+        doTestGetActiveNetworkInfo(
+                changeEnabled = true,
+                permissions = PERMISSION_NONE,
+                expectBlocked = true
+        )
+        doTestGetActiveNetworkInfo(
+                changeEnabled = false,
+                permissions = PERMISSION_INTERNET,
+                expectBlocked = true
+        )
+        // Network access is considered not blocked if the compat change is disabled and an app
+        // does not have PERMISSION_INTERNET
+        doTestGetActiveNetworkInfo(
+                changeEnabled = false,
+                permissions = PERMISSION_NONE,
+                expectBlocked = false
+        )
+    }
+}
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
index 6425daa..9026481 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
@@ -314,7 +314,7 @@
             new SetFeatureFlagsRule((name, enabled) -> {
                 mFeatureFlags.put(name, enabled);
                 return null;
-            });
+            }, (name) -> mFeatureFlags.getOrDefault(name, false));
 
     private class MockContext extends BroadcastInterceptingContext {
         private final Context mBaseContext;
diff --git a/thread/TEST_MAPPING b/thread/TEST_MAPPING
index ebbb9af..34d67bb 100644
--- a/thread/TEST_MAPPING
+++ b/thread/TEST_MAPPING
@@ -11,5 +11,8 @@
     }
   ],
   "postsubmit": [
+    {
+      "name": "ThreadNetworkMultiDeviceTests"
+    }
   ]
 }
diff --git a/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java b/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
index 0559499..0c200fd 100644
--- a/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
+++ b/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
@@ -106,6 +106,7 @@
 import android.os.Looper;
 import android.os.RemoteException;
 import android.os.UserManager;
+import android.provider.Settings;
 import android.util.Log;
 import android.util.SparseArray;
 
@@ -121,6 +122,7 @@
 import com.android.server.thread.openthread.IOtStatusReceiver;
 import com.android.server.thread.openthread.Ipv6AddressInfo;
 import com.android.server.thread.openthread.MeshcopTxtAttributes;
+import com.android.server.thread.openthread.OnMeshPrefixConfig;
 import com.android.server.thread.openthread.OtDaemonState;
 
 import libcore.util.HexEncoding;
@@ -199,6 +201,7 @@
     private final ThreadPersistentSettings mPersistentSettings;
     private final UserManager mUserManager;
     private boolean mUserRestricted;
+    private boolean mAirplaneModeOn;
     private boolean mForceStopOtDaemonEnabled;
 
     private BorderRouterConfigurationParcel mBorderRouterConfig;
@@ -282,7 +285,7 @@
     }
 
     private void maybeInitializeOtDaemon() {
-        if (!isEnabled()) {
+        if (!shouldEnableThread()) {
             return;
         }
 
@@ -317,7 +320,7 @@
 
         otDaemon.initialize(
                 mTunIfController.getTunFd(),
-                isEnabled(),
+                shouldEnableThread(),
                 mNsdPublisher,
                 getMeshcopTxtAttributes(mResources.get()),
                 mOtDaemonCallbackProxy,
@@ -382,7 +385,7 @@
                     Log.d(
                             TAG,
                             "Initializing Thread system service: Thread is "
-                                    + (isEnabled() ? "enabled" : "disabled"));
+                                    + (shouldEnableThread() ? "enabled" : "disabled"));
                     try {
                         mTunIfController.createTunInterface();
                     } catch (IOException e) {
@@ -394,6 +397,8 @@
                     requestThreadNetwork();
                     mUserRestricted = isThreadUserRestricted();
                     registerUserRestrictionsReceiver();
+                    mAirplaneModeOn = isAirplaneModeOn();
+                    registerAirplaneModeReceiver();
                     maybeInitializeOtDaemon();
                 });
     }
@@ -474,6 +479,15 @@
             // the otDaemon set enabled state operation succeeded or not, so that it can recover
             // to the desired value after reboot.
             mPersistentSettings.put(ThreadPersistentSettings.THREAD_ENABLED.key, isEnabled);
+
+            // Remember whether the user wanted to keep Thread enabled in airplane mode. If once
+            // the user disabled Thread again in airplane mode, the persistent settings state is
+            // reset (so that Thread will be auto-disabled again when airplane mode is turned on).
+            // This behavior is consistent with Wi-Fi and bluetooth.
+            if (mAirplaneModeOn) {
+                mPersistentSettings.put(
+                        ThreadPersistentSettings.THREAD_ENABLED_IN_AIRPLANE_MODE.key, isEnabled);
+            }
         }
 
         try {
@@ -510,36 +524,30 @@
                         + newUserRestrictedState);
         mUserRestricted = newUserRestrictedState;
 
-        final boolean isEnabled = isEnabled();
+        final boolean shouldEnableThread = shouldEnableThread();
         final IOperationReceiver receiver =
                 new IOperationReceiver.Stub() {
                     @Override
                     public void onSuccess() {
                         Log.d(
                                 TAG,
-                                (isEnabled ? "Enabled" : "Disabled")
+                                (shouldEnableThread ? "Enabled" : "Disabled")
                                         + " Thread due to user restriction change");
                     }
 
                     @Override
-                    public void onError(int otError, String messages) {
+                    public void onError(int errorCode, String errorMessage) {
                         Log.e(
                                 TAG,
                                 "Failed to "
-                                        + (isEnabled ? "enable" : "disable")
+                                        + (shouldEnableThread ? "enable" : "disable")
                                         + " Thread for user restriction change");
                     }
                 };
         // Do not save the user restriction state to persistent settings so that the user
         // configuration won't be overwritten
-        setEnabledInternal(isEnabled, false /* persist */, new OperationReceiverWrapper(receiver));
-    }
-
-    /** Returns {@code true} if Thread is set enabled. */
-    private boolean isEnabled() {
-        return !mForceStopOtDaemonEnabled
-                && !mUserRestricted
-                && mPersistentSettings.get(ThreadPersistentSettings.THREAD_ENABLED);
+        setEnabledInternal(
+                shouldEnableThread, false /* persist */, new OperationReceiverWrapper(receiver));
     }
 
     /** Returns {@code true} if Thread has been restricted for the user. */
@@ -547,6 +555,74 @@
         return mUserManager.hasUserRestriction(DISALLOW_THREAD_NETWORK);
     }
 
+    private void registerAirplaneModeReceiver() {
+        mContext.registerReceiver(
+                new BroadcastReceiver() {
+                    @Override
+                    public void onReceive(Context context, Intent intent) {
+                        onAirplaneModeChanged(isAirplaneModeOn());
+                    }
+                },
+                new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED),
+                null /* broadcastPermission */,
+                mHandler);
+    }
+
+    private void onAirplaneModeChanged(boolean newAirplaneModeOn) {
+        checkOnHandlerThread();
+        if (mAirplaneModeOn == newAirplaneModeOn) {
+            return;
+        }
+        Log.i(TAG, "Airplane mode changed: " + mAirplaneModeOn + " -> " + newAirplaneModeOn);
+        mAirplaneModeOn = newAirplaneModeOn;
+
+        final boolean shouldEnableThread = shouldEnableThread();
+        final IOperationReceiver receiver =
+                new IOperationReceiver.Stub() {
+                    @Override
+                    public void onSuccess() {
+                        Log.d(
+                                TAG,
+                                (shouldEnableThread ? "Enabled" : "Disabled")
+                                        + " Thread due to airplane mode change");
+                    }
+
+                    @Override
+                    public void onError(int errorCode, String errorMessage) {
+                        Log.e(
+                                TAG,
+                                "Failed to "
+                                        + (shouldEnableThread ? "enable" : "disable")
+                                        + " Thread for airplane mode change");
+                    }
+                };
+        // Do not save the user restriction state to persistent settings so that the user
+        // configuration won't be overwritten
+        setEnabledInternal(
+                shouldEnableThread, false /* persist */, new OperationReceiverWrapper(receiver));
+    }
+
+    /** Returns {@code true} if Airplane mode has been turned on. */
+    private boolean isAirplaneModeOn() {
+        return Settings.Global.getInt(
+                        mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 0)
+                == 1;
+    }
+
+    /**
+     * Returns {@code true} if Thread should be enabled based on current settings, runtime user
+     * restriction and airplane mode state.
+     */
+    private boolean shouldEnableThread() {
+        final boolean enabledInAirplaneMode =
+                mPersistentSettings.get(ThreadPersistentSettings.THREAD_ENABLED_IN_AIRPLANE_MODE);
+
+        return !mForceStopOtDaemonEnabled
+                && !mUserRestricted
+                && (!mAirplaneModeOn || enabledInAirplaneMode)
+                && mPersistentSettings.get(ThreadPersistentSettings.THREAD_ENABLED);
+    }
+
     private void requestUpstreamNetwork() {
         if (mUpstreamNetworkCallback != null) {
             throw new AssertionError("The upstream network request is already there.");
@@ -1018,7 +1094,7 @@
         checkOnHandlerThread();
 
         // Fails early to avoid waking up ot-daemon by the ThreadNetworkCountryCode class
-        if (!isEnabled()) {
+        if (!shouldEnableThread()) {
             receiver.onError(
                     ERROR_THREAD_DISABLED, "Can't set country code when Thread is disabled");
             return;
@@ -1159,6 +1235,18 @@
         }
     }
 
+    private void handlePrefixChanged(List<OnMeshPrefixConfig> onMeshPrefixConfigList) {
+        checkOnHandlerThread();
+
+        mTunIfController.updatePrefixes(onMeshPrefixConfigList);
+
+        // The OT daemon can send link property updates before the networkAgent is
+        // registered
+        if (mNetworkAgent != null) {
+            mNetworkAgent.sendLinkProperties(mTunIfController.getLinkProperties());
+        }
+    }
+
     private void sendLocalNetworkConfig() {
         if (mNetworkAgent == null) {
             return;
@@ -1283,7 +1371,6 @@
         private void notifyThreadEnabledUpdated(IStateCallback callback, int enabledState) {
             try {
                 callback.onThreadEnableStateChanged(enabledState);
-                Log.i(TAG, "onThreadEnableStateChanged " + enabledState);
             } catch (RemoteException ignored) {
                 // do nothing if the client is dead
             }
@@ -1507,5 +1594,10 @@
         public void onBackboneRouterStateChanged(BackboneRouterState state) {
             mHandler.post(() -> handleMulticastForwardingChanged(state));
         }
+
+        @Override
+        public void onPrefixChanged(List<OnMeshPrefixConfig> onMeshPrefixConfigList) {
+            mHandler.post(() -> handlePrefixChanged(onMeshPrefixConfigList));
+        }
     }
 }
diff --git a/thread/service/java/com/android/server/thread/ThreadPersistentSettings.java b/thread/service/java/com/android/server/thread/ThreadPersistentSettings.java
index 8aaff60..f18aac9 100644
--- a/thread/service/java/com/android/server/thread/ThreadPersistentSettings.java
+++ b/thread/service/java/com/android/server/thread/ThreadPersistentSettings.java
@@ -63,6 +63,14 @@
     /** Stores the Thread feature toggle state, true for enabled and false for disabled. */
     public static final Key<Boolean> THREAD_ENABLED = new Key<>("thread_enabled", true);
 
+    /**
+     * Indicates that Thread was enabled (i.e. via the setEnabled() API) when the airplane mode is
+     * turned on in settings. When this value is {@code true}, the current airplane mode state will
+     * be ignored when evaluating the Thread enabled state.
+     */
+    public static final Key<Boolean> THREAD_ENABLED_IN_AIRPLANE_MODE =
+            new Key<>("thread_enabled_in_airplane_mode", false);
+
     /** Stores the Thread country code, null if no country code is stored. */
     public static final Key<String> THREAD_COUNTRY_CODE = new Key<>("thread_country_code", null);
 
diff --git a/thread/service/java/com/android/server/thread/TunInterfaceController.java b/thread/service/java/com/android/server/thread/TunInterfaceController.java
index dec72b2..976f93d 100644
--- a/thread/service/java/com/android/server/thread/TunInterfaceController.java
+++ b/thread/service/java/com/android/server/thread/TunInterfaceController.java
@@ -16,29 +16,40 @@
 
 package com.android.server.thread;
 
+import static android.system.OsConstants.AF_INET6;
 import static android.system.OsConstants.EADDRINUSE;
+import static android.system.OsConstants.IFF_MULTICAST;
+import static android.system.OsConstants.IFF_NOARP;
+import static android.system.OsConstants.NETLINK_ROUTE;
+
+import static com.android.net.module.util.netlink.NetlinkConstants.RTM_NEWLINK;
+import static com.android.net.module.util.netlink.RtNetlinkLinkMessage.IFLA_AF_SPEC;
+import static com.android.net.module.util.netlink.RtNetlinkLinkMessage.IFLA_INET6_ADDR_GEN_MODE;
+import static com.android.net.module.util.netlink.RtNetlinkLinkMessage.IN6_ADDR_GEN_MODE_NONE;
+import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_ACK;
+import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_REQUEST;
 
 import android.annotation.Nullable;
 import android.net.IpPrefix;
 import android.net.LinkAddress;
 import android.net.LinkProperties;
 import android.net.RouteInfo;
-import android.net.util.SocketUtils;
 import android.os.ParcelFileDescriptor;
 import android.os.SystemClock;
 import android.system.ErrnoException;
 import android.system.Os;
-import android.system.OsConstants;
 import android.util.Log;
 
+import com.android.net.module.util.HexDump;
 import com.android.net.module.util.LinkPropertiesUtils.CompareResult;
 import com.android.net.module.util.netlink.NetlinkUtils;
-import com.android.net.module.util.netlink.RtNetlinkAddressMessage;
+import com.android.net.module.util.netlink.StructIfinfoMsg;
+import com.android.net.module.util.netlink.StructNlAttr;
+import com.android.net.module.util.netlink.StructNlMsgHdr;
 import com.android.server.thread.openthread.Ipv6AddressInfo;
+import com.android.server.thread.openthread.OnMeshPrefixConfig;
 
-import java.io.FileDescriptor;
 import java.io.IOException;
-import java.io.InterruptedIOException;
 import java.net.Inet6Address;
 import java.net.InetAddress;
 import java.net.InetSocketAddress;
@@ -46,12 +57,15 @@
 import java.net.NetworkInterface;
 import java.net.SocketException;
 import java.net.UnknownHostException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
 import java.util.ArrayList;
 import java.util.List;
 
 /** Controller for virtual/tunnel network interfaces. */
 public class TunInterfaceController {
     private static final String TAG = "TunIfController";
+    private static final boolean DBG = false;
     private static final long INFINITE_LIFETIME = 0xffffffffL;
     static final int MTU = 1280;
 
@@ -61,12 +75,12 @@
 
     private final String mIfName;
     private final LinkProperties mLinkProperties = new LinkProperties();
-    private ParcelFileDescriptor mParcelTunFd;
-    private FileDescriptor mNetlinkSocket;
-    private static int sNetlinkSeqNo = 0;
     private final MulticastSocket mMulticastSocket; // For join group and leave group
+    private final List<InetAddress> mMulticastAddresses = new ArrayList<>();
+    private final List<RouteInfo> mNetDataPrefixes = new ArrayList<>();
+
+    private ParcelFileDescriptor mParcelTunFd;
     private NetworkInterface mNetworkInterface;
-    private List<InetAddress> mMulticastAddresses = new ArrayList<>();
 
     /** Creates a new {@link TunInterfaceController} instance for given interface. */
     public TunInterfaceController(String interfaceName) {
@@ -89,26 +103,21 @@
     public void createTunInterface() throws IOException {
         mParcelTunFd = ParcelFileDescriptor.adoptFd(nativeCreateTunInterface(mIfName, MTU));
         try {
-            mNetlinkSocket = NetlinkUtils.netlinkSocketForProto(OsConstants.NETLINK_ROUTE);
-        } catch (ErrnoException e) {
-            throw new IOException("Failed to create netlink socket", e);
-        }
-        try {
             mNetworkInterface = NetworkInterface.getByName(mIfName);
         } catch (SocketException e) {
             throw new IOException("Failed to get NetworkInterface", e);
         }
+
+        setAddrGenModeToNone();
     }
 
     public void destroyTunInterface() {
         try {
             mParcelTunFd.close();
-            SocketUtils.closeSocket(mNetlinkSocket);
         } catch (IOException e) {
             // Should never fail
         }
         mParcelTunFd = null;
-        mNetlinkSocket = null;
         mNetworkInterface = null;
     }
 
@@ -126,6 +135,10 @@
             for (LinkAddress address : mLinkProperties.getAllLinkAddresses()) {
                 removeAddress(address);
             }
+            for (RouteInfo route : mLinkProperties.getAllRoutes()) {
+                mLinkProperties.removeRoute(route);
+            }
+            mNetDataPrefixes.clear();
         }
         nativeSetInterfaceUp(mIfName, isUp);
     }
@@ -136,14 +149,14 @@
     public void addAddress(LinkAddress address) {
         Log.d(TAG, "Adding address " + address + " with flags: " + address.getFlags());
 
-        long validLifetimeSeconds;
         long preferredLifetimeSeconds;
+        long validLifetimeSeconds;
 
         if (address.getDeprecationTime() == LinkAddress.LIFETIME_PERMANENT
                 || address.getDeprecationTime() == LinkAddress.LIFETIME_UNKNOWN) {
-            validLifetimeSeconds = INFINITE_LIFETIME;
+            preferredLifetimeSeconds = INFINITE_LIFETIME;
         } else {
-            validLifetimeSeconds =
+            preferredLifetimeSeconds =
                     Math.max(
                             (address.getDeprecationTime() - SystemClock.elapsedRealtime()) / 1000L,
                             0L);
@@ -151,28 +164,23 @@
 
         if (address.getExpirationTime() == LinkAddress.LIFETIME_PERMANENT
                 || address.getExpirationTime() == LinkAddress.LIFETIME_UNKNOWN) {
-            preferredLifetimeSeconds = INFINITE_LIFETIME;
+            validLifetimeSeconds = INFINITE_LIFETIME;
         } else {
-            preferredLifetimeSeconds =
+            validLifetimeSeconds =
                     Math.max(
                             (address.getExpirationTime() - SystemClock.elapsedRealtime()) / 1000L,
                             0L);
         }
 
-        byte[] message =
-                RtNetlinkAddressMessage.newRtmNewAddressMessage(
-                        sNetlinkSeqNo++,
-                        address.getAddress(),
-                        (short) address.getPrefixLength(),
-                        address.getFlags(),
-                        (byte) address.getScope(),
-                        Os.if_nametoindex(mIfName),
-                        validLifetimeSeconds,
-                        preferredLifetimeSeconds);
-        try {
-            Os.write(mNetlinkSocket, message, 0, message.length);
-        } catch (ErrnoException | InterruptedIOException e) {
-            Log.e(TAG, "Failed to add address " + address, e);
+        if (!NetlinkUtils.sendRtmNewAddressRequest(
+                Os.if_nametoindex(mIfName),
+                address.getAddress(),
+                (short) address.getPrefixLength(),
+                address.getFlags(),
+                (byte) address.getScope(),
+                preferredLifetimeSeconds,
+                validLifetimeSeconds)) {
+            Log.w(TAG, "Failed to add address " + address.getAddress().getHostAddress());
             return;
         }
         mLinkProperties.addLinkAddress(address);
@@ -182,22 +190,17 @@
     /** Removes an address from the interface. */
     public void removeAddress(LinkAddress address) {
         Log.d(TAG, "Removing address " + address);
-        byte[] message =
-                RtNetlinkAddressMessage.newRtmDelAddressMessage(
-                        sNetlinkSeqNo++,
-                        address.getAddress(),
-                        (short) address.getPrefixLength(),
-                        Os.if_nametoindex(mIfName));
 
         // Intentionally update the mLinkProperties before send netlink message because the
         // address is already removed from ot-daemon and apps can't reach to the address even
         // when the netlink request below fails
         mLinkProperties.removeLinkAddress(address);
         mLinkProperties.removeRoute(getRouteForAddress(address));
-        try {
-            Os.write(mNetlinkSocket, message, 0, message.length);
-        } catch (ErrnoException | InterruptedIOException e) {
-            Log.e(TAG, "Failed to remove address " + address, e);
+        if (!NetlinkUtils.sendRtmDelAddressRequest(
+                Os.if_nametoindex(mIfName),
+                (Inet6Address) address.getAddress(),
+                (short) address.getPrefixLength())) {
+            Log.w(TAG, "Failed to remove address " + address.getAddress().getHostAddress());
         }
     }
 
@@ -243,13 +246,40 @@
         mMulticastAddresses.addAll(newMulticastAddresses);
     }
 
+    public void updatePrefixes(List<OnMeshPrefixConfig> onMeshPrefixConfigList) {
+        final List<RouteInfo> newNetDataPrefixes = new ArrayList<>();
+
+        for (OnMeshPrefixConfig onMeshPrefixConfig : onMeshPrefixConfigList) {
+            newNetDataPrefixes.add(getRouteForOnMeshPrefix(onMeshPrefixConfig));
+        }
+
+        final CompareResult<RouteInfo> prefixDiff =
+                new CompareResult<>(mNetDataPrefixes, newNetDataPrefixes);
+        for (RouteInfo routeRemoved : prefixDiff.removed) {
+            mLinkProperties.removeRoute(routeRemoved);
+        }
+        for (RouteInfo routeAdded : prefixDiff.added) {
+            mLinkProperties.addRoute(routeAdded);
+        }
+
+        mNetDataPrefixes.clear();
+        mNetDataPrefixes.addAll(newNetDataPrefixes);
+    }
+
     private RouteInfo getRouteForAddress(LinkAddress linkAddress) {
-        return new RouteInfo(
-                new IpPrefix(linkAddress.getAddress(), linkAddress.getPrefixLength()),
-                null,
-                mIfName,
-                RouteInfo.RTN_UNICAST,
-                MTU);
+        return getRouteForIpPrefix(
+                new IpPrefix(linkAddress.getAddress(), linkAddress.getPrefixLength()));
+    }
+
+    private RouteInfo getRouteForOnMeshPrefix(OnMeshPrefixConfig onMeshPrefixConfig) {
+        return getRouteForIpPrefix(
+                new IpPrefix(
+                        bytesToInet6Address(onMeshPrefixConfig.prefix),
+                        onMeshPrefixConfig.prefixLength));
+    }
+
+    private RouteInfo getRouteForIpPrefix(IpPrefix ipPrefix) {
+        return new RouteInfo(ipPrefix, null, mIfName, RouteInfo.RTN_UNICAST, MTU);
     }
 
     /** Called by {@link ThreadNetworkControllerService} to do clean up when ot-daemon is dead. */
@@ -333,4 +363,66 @@
             Log.e(TAG, "failed to leave group " + address.getHostAddress(), e);
         }
     }
+
+    /**
+     * Sets the address generation mode to {@code IN6_ADDR_GEN_MODE_NONE}.
+     *
+     * <p>So that the "thread-wpan" interface has only one IPv6 link local address which is
+     * generated by OpenThread.
+     */
+    private void setAddrGenModeToNone() {
+        StructNlMsgHdr header = new StructNlMsgHdr();
+        header.nlmsg_type = RTM_NEWLINK;
+        header.nlmsg_pid = 0;
+        header.nlmsg_seq = 0;
+        header.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
+
+        StructIfinfoMsg ifInfo =
+                new StructIfinfoMsg(
+                        (short) 0 /* family */,
+                        0 /* type */,
+                        Os.if_nametoindex(mIfName),
+                        (IFF_MULTICAST | IFF_NOARP) /* flags */,
+                        0xffffffff /* change */);
+
+        // Nested attributes
+        // IFLA_AF_SPEC
+        //   AF_INET6
+        //     IFLA_INET6_ADDR_GEN_MODE
+        StructNlAttr addrGenMode =
+                new StructNlAttr(IFLA_INET6_ADDR_GEN_MODE, (byte) IN6_ADDR_GEN_MODE_NONE);
+        StructNlAttr afInet6 = new StructNlAttr((short) AF_INET6, addrGenMode);
+        StructNlAttr afSpec = new StructNlAttr(IFLA_AF_SPEC, afInet6);
+
+        final int msgLength =
+                StructNlMsgHdr.STRUCT_SIZE
+                        + StructIfinfoMsg.STRUCT_SIZE
+                        + afSpec.getAlignedLength();
+        byte[] msg = new byte[msgLength];
+        ByteBuffer buf = ByteBuffer.wrap(msg);
+        buf.order(ByteOrder.nativeOrder());
+
+        header.nlmsg_len = msgLength;
+        header.pack(buf);
+        ifInfo.pack(buf);
+        afSpec.pack(buf);
+
+        if (buf.position() != msgLength) {
+            throw new AssertionError(
+                    String.format(
+                            "Unexpected netlink message size (actual = %d, expected = %d)",
+                            buf.position(), msgLength));
+        }
+
+        if (DBG) {
+            Log.d(TAG, "ADDR_GEN_MODE message is:");
+            Log.d(TAG, HexDump.dumpHexString(msg));
+        }
+
+        try {
+            NetlinkUtils.sendOneShotKernelMessage(NETLINK_ROUTE, msg);
+        } catch (ErrnoException e) {
+            Log.e(TAG, "Failed to set ADDR_GEN_MODE to NONE", e);
+        }
+    }
 }
diff --git a/thread/tests/integration/src/android/net/thread/ThreadIntegrationTest.java b/thread/tests/integration/src/android/net/thread/ThreadIntegrationTest.java
index e211e22..61b6eac 100644
--- a/thread/tests/integration/src/android/net/thread/ThreadIntegrationTest.java
+++ b/thread/tests/integration/src/android/net/thread/ThreadIntegrationTest.java
@@ -22,6 +22,8 @@
 import static android.net.thread.utils.IntegrationTestUtils.CALLBACK_TIMEOUT;
 import static android.net.thread.utils.IntegrationTestUtils.RESTART_JOIN_TIMEOUT;
 import static android.net.thread.utils.IntegrationTestUtils.getIpv6LinkAddresses;
+import static android.net.thread.utils.IntegrationTestUtils.getPrefixesFromNetData;
+import static android.net.thread.utils.IntegrationTestUtils.getThreadNetwork;
 import static android.net.thread.utils.IntegrationTestUtils.isInMulticastGroup;
 import static android.net.thread.utils.IntegrationTestUtils.waitFor;
 
@@ -31,11 +33,14 @@
 
 import static com.google.common.io.BaseEncoding.base16;
 import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
 
 import android.content.Context;
+import android.net.ConnectivityManager;
 import android.net.InetAddresses;
 import android.net.IpPrefix;
 import android.net.LinkAddress;
+import android.net.LinkProperties;
 import android.net.thread.utils.FullThreadDevice;
 import android.net.thread.utils.OtDaemonController;
 import android.net.thread.utils.ThreadFeatureCheckerRule;
@@ -75,6 +80,9 @@
     // The maximum time for OT addresses to be propagated to the TUN interface "thread-wpan"
     private static final Duration TUN_ADDR_UPDATE_TIMEOUT = Duration.ofSeconds(1);
 
+    // The maximum time for changes to be propagated to netdata.
+    private static final Duration NET_DATA_UPDATE_TIMEOUT = Duration.ofSeconds(1);
+
     // A valid Thread Active Operational Dataset generated from OpenThread CLI "dataset init new".
     private static final byte[] DEFAULT_DATASET_TLVS =
             base16().decode(
@@ -89,6 +97,10 @@
     private static final Inet6Address GROUP_ADDR_ALL_ROUTERS =
             (Inet6Address) InetAddresses.parseNumericAddress("ff02::2");
 
+    private static final String TEST_NO_SLAAC_PREFIX = "9101:dead:beef:cafe::/64";
+    private static final InetAddress TEST_NO_SLAAC_PREFIX_ADDRESS =
+            InetAddresses.parseNumericAddress("9101:dead:beef:cafe::");
+
     @Rule public final ThreadFeatureCheckerRule mThreadRule = new ThreadFeatureCheckerRule();
 
     private ExecutorService mExecutor;
@@ -164,7 +176,7 @@
 
     // TODO (b/323300829): add test for removing an OT address
     @Test
-    public void tunInterface_joinedNetwork_otAddressesAddedToTunInterface() throws Exception {
+    public void tunInterface_joinedNetwork_otAndTunAddressesMatch() throws Exception {
         mController.joinAndWait(DEFAULT_DATASET);
 
         List<Inet6Address> otAddresses = mOtCtl.getAddresses();
@@ -173,9 +185,12 @@
         // that we can write assertThat() in the Predicate
         waitFor(
                 () -> {
-                    String ifconfig = runShellCommand("ifconfig thread-wpan");
-                    return otAddresses.stream()
-                            .allMatch(addr -> ifconfig.contains(addr.getHostAddress()));
+                    List<Inet6Address> tunAddresses =
+                            getIpv6LinkAddresses("thread-wpan").stream()
+                                    .map(linkAddr -> (Inet6Address) linkAddr.getAddress())
+                                    .toList();
+                    return otAddresses.containsAll(tunAddresses)
+                            && tunAddresses.containsAll(otAddresses);
                 },
                 TUN_ADDR_UPDATE_TIMEOUT);
     }
@@ -237,6 +252,81 @@
         assertTunInterfaceMemberOfGroup(GROUP_ADDR_ALL_ROUTERS);
     }
 
+    @Test
+    public void edPingsMeshLocalAddresses_oneReplyPerRequest() throws Exception {
+        mController.joinAndWait(DEFAULT_DATASET);
+        startFtdChild(mFtd, DEFAULT_DATASET);
+        List<Inet6Address> meshLocalAddresses = mOtCtl.getMeshLocalAddresses();
+
+        for (Inet6Address address : meshLocalAddresses) {
+            assertWithMessage(
+                            "There may be duplicated replies of ping request to "
+                                    + address.getHostAddress())
+                    .that(mFtd.ping(address, 2))
+                    .isEqualTo(2);
+        }
+    }
+
+    @Test
+    public void addPrefixToNetData_routeIsAddedToTunInterface() throws Exception {
+        ConnectivityManager cm = mContext.getSystemService(ConnectivityManager.class);
+        mController.joinAndWait(DEFAULT_DATASET);
+
+        // Ftd child doesn't have the ability to add a prefix, so let BR itself add a prefix.
+        mOtCtl.executeCommand("prefix add " + TEST_NO_SLAAC_PREFIX + " pros med");
+        mOtCtl.executeCommand("netdata register");
+        waitFor(
+                () -> {
+                    String netData = mOtCtl.executeCommand("netdata show");
+                    return getPrefixesFromNetData(netData).contains(TEST_NO_SLAAC_PREFIX);
+                },
+                NET_DATA_UPDATE_TIMEOUT);
+
+        LinkProperties lp = cm.getLinkProperties(getThreadNetwork(CALLBACK_TIMEOUT));
+        assertThat(lp).isNotNull();
+        assertThat(lp.getRoutes().stream().anyMatch(r -> r.matches(TEST_NO_SLAAC_PREFIX_ADDRESS)))
+                .isTrue();
+    }
+
+    @Test
+    public void removePrefixFromNetData_routeIsRemovedFromTunInterface() throws Exception {
+        ConnectivityManager cm = mContext.getSystemService(ConnectivityManager.class);
+        mController.joinAndWait(DEFAULT_DATASET);
+        mOtCtl.executeCommand("prefix add " + TEST_NO_SLAAC_PREFIX + " pros med");
+        mOtCtl.executeCommand("netdata register");
+
+        mOtCtl.executeCommand("prefix remove " + TEST_NO_SLAAC_PREFIX);
+        mOtCtl.executeCommand("netdata register");
+        waitFor(
+                () -> {
+                    String netData = mOtCtl.executeCommand("netdata show");
+                    return !getPrefixesFromNetData(netData).contains(TEST_NO_SLAAC_PREFIX);
+                },
+                NET_DATA_UPDATE_TIMEOUT);
+
+        LinkProperties lp = cm.getLinkProperties(getThreadNetwork(CALLBACK_TIMEOUT));
+        assertThat(lp).isNotNull();
+        assertThat(lp.getRoutes().stream().anyMatch(r -> r.matches(TEST_NO_SLAAC_PREFIX_ADDRESS)))
+                .isFalse();
+    }
+
+    @Test
+    public void toggleThreadNetwork_routeFromPreviousNetDataIsRemoved() throws Exception {
+        ConnectivityManager cm = mContext.getSystemService(ConnectivityManager.class);
+        mController.joinAndWait(DEFAULT_DATASET);
+        mOtCtl.executeCommand("prefix add " + TEST_NO_SLAAC_PREFIX + " pros med");
+        mOtCtl.executeCommand("netdata register");
+
+        mController.leaveAndWait();
+        mOtCtl.factoryReset();
+        mController.joinAndWait(DEFAULT_DATASET);
+
+        LinkProperties lp = cm.getLinkProperties(getThreadNetwork(CALLBACK_TIMEOUT));
+        assertThat(lp).isNotNull();
+        assertThat(lp.getRoutes().stream().anyMatch(r -> r.matches(TEST_NO_SLAAC_PREFIX_ADDRESS)))
+                .isFalse();
+    }
+
     // TODO (b/323300829): add more tests for integration with linux platform and
     // ConnectivityService
 
diff --git a/thread/tests/integration/src/android/net/thread/utils/FullThreadDevice.java b/thread/tests/integration/src/android/net/thread/utils/FullThreadDevice.java
index 5e70f6c..46cf562 100644
--- a/thread/tests/integration/src/android/net/thread/utils/FullThreadDevice.java
+++ b/thread/tests/integration/src/android/net/thread/utils/FullThreadDevice.java
@@ -57,7 +57,9 @@
     private static final int PING_SIZE = 100;
     // There may not be a response for the ping command, using a short timeout to keep the tests
     // short.
-    private static final float PING_TIMEOUT_SECONDS = 0.1f;
+    private static final float PING_TIMEOUT_0_1_SECOND = 0.1f;
+    // 1 second timeout should be used when response is expected.
+    private static final float PING_TIMEOUT_1_SECOND = 1f;
 
     private final Process mProcess;
     private final BufferedReader mReader;
@@ -78,9 +80,10 @@
      */
     public FullThreadDevice(int nodeId) {
         try {
-            mProcess = Runtime.getRuntime().exec("/system/bin/ot-cli-ftd " + nodeId);
+            mProcess = Runtime.getRuntime().exec("/system/bin/ot-cli-ftd -Leth1 " + nodeId);
         } catch (IOException e) {
-            throw new IllegalStateException("Failed to start ot-cli-ftd (id=" + nodeId + ")", e);
+            throw new IllegalStateException(
+                    "Failed to start ot-cli-ftd -Leth1 (id=" + nodeId + ")", e);
         }
         mReader = new BufferedReader(new InputStreamReader(mProcess.getInputStream()));
         mWriter = new BufferedWriter(new OutputStreamWriter(mProcess.getOutputStream()));
@@ -408,7 +411,7 @@
                 1 /* count */,
                 PING_INTERVAL,
                 HOP_LIMIT,
-                PING_TIMEOUT_SECONDS);
+                PING_TIMEOUT_0_1_SECOND);
     }
 
     public void ping(Inet6Address address) {
@@ -419,10 +422,24 @@
                 1 /* count */,
                 PING_INTERVAL,
                 HOP_LIMIT,
-                PING_TIMEOUT_SECONDS);
+                PING_TIMEOUT_0_1_SECOND);
     }
 
-    private void ping(
+    /** Returns the number of ping reply packets received. */
+    public int ping(Inet6Address address, int count) {
+        List<String> output =
+                ping(
+                        address,
+                        null,
+                        PING_SIZE,
+                        count,
+                        PING_INTERVAL,
+                        HOP_LIMIT,
+                        PING_TIMEOUT_1_SECOND);
+        return getReceivedPacketsCount(output);
+    }
+
+    private List<String> ping(
             Inet6Address address,
             Inet6Address source,
             int size,
@@ -445,7 +462,21 @@
                         + hopLimit
                         + " "
                         + timeout;
-        executeCommand(cmd);
+        return executeCommand(cmd);
+    }
+
+    private int getReceivedPacketsCount(List<String> stringList) {
+        Pattern pattern = Pattern.compile("([\\d]+) packets received");
+
+        for (String message : stringList) {
+            Matcher matcher = pattern.matcher(message);
+            if (matcher.find()) {
+                String packetCountStr = matcher.group(1);
+                return Integer.parseInt(packetCountStr);
+            }
+        }
+        // No match found
+        return -1;
     }
 
     @FormatMethod
diff --git a/thread/tests/integration/src/android/net/thread/utils/IntegrationTestUtils.java b/thread/tests/integration/src/android/net/thread/utils/IntegrationTestUtils.java
index 9be9566..ada46c8 100644
--- a/thread/tests/integration/src/android/net/thread/utils/IntegrationTestUtils.java
+++ b/thread/tests/integration/src/android/net/thread/utils/IntegrationTestUtils.java
@@ -15,6 +15,7 @@
  */
 package android.net.thread.utils;
 
+import static android.net.NetworkCapabilities.NET_CAPABILITY_LOCAL_NETWORK;
 import static android.system.OsConstants.IPPROTO_ICMPV6;
 
 import static com.android.compatibility.common.util.SystemUtil.runShellCommandOrThrow;
@@ -24,17 +25,24 @@
 import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
 
 import static java.util.concurrent.TimeUnit.MILLISECONDS;
+import static java.util.concurrent.TimeUnit.SECONDS;
 
+import android.net.ConnectivityManager;
 import android.net.InetAddresses;
 import android.net.LinkAddress;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
 import android.net.TestNetworkInterface;
 import android.net.nsd.NsdManager;
 import android.net.nsd.NsdServiceInfo;
 import android.net.thread.ThreadNetworkController;
+import android.os.Build;
 import android.os.Handler;
 import android.os.SystemClock;
 
 import androidx.annotation.NonNull;
+import androidx.test.core.app.ApplicationProvider;
 
 import com.android.net.module.util.Struct;
 import com.android.net.module.util.structs.Icmpv6Header;
@@ -293,7 +301,7 @@
         return false;
     }
 
-    public static List<LinkAddress> getIpv6LinkAddresses(String interfaceName) throws IOException {
+    public static List<LinkAddress> getIpv6LinkAddresses(String interfaceName) {
         List<LinkAddress> addresses = new ArrayList<>();
         final String cmd = " ip -6 addr show dev " + interfaceName;
         final String output = runShellCommandOrThrow(cmd);
@@ -375,6 +383,36 @@
         }
     }
 
+    public static String getPrefixesFromNetData(String netData) {
+        int startIdx = netData.indexOf("Prefixes:");
+        int endIdx = netData.indexOf("Routes:");
+        return netData.substring(startIdx, endIdx);
+    }
+
+    public static Network getThreadNetwork(Duration timeout) throws Exception {
+        CompletableFuture<Network> networkFuture = new CompletableFuture<>();
+        ConnectivityManager cm =
+                ApplicationProvider.getApplicationContext()
+                        .getSystemService(ConnectivityManager.class);
+        NetworkRequest.Builder networkRequestBuilder =
+                new NetworkRequest.Builder().addTransportType(NetworkCapabilities.TRANSPORT_THREAD);
+        // Before V, we need to explicitly set `NET_CAPABILITY_LOCAL_NETWORK` capability to request
+        // a Thread network.
+        if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
+            networkRequestBuilder.addCapability(NET_CAPABILITY_LOCAL_NETWORK);
+        }
+        NetworkRequest networkRequest = networkRequestBuilder.build();
+        ConnectivityManager.NetworkCallback networkCallback =
+                new ConnectivityManager.NetworkCallback() {
+                    @Override
+                    public void onAvailable(Network network) {
+                        networkFuture.complete(network);
+                    }
+                };
+        cm.registerNetworkCallback(networkRequest, networkCallback);
+        return networkFuture.get(timeout.toSeconds(), SECONDS);
+    }
+
     private static class DefaultDiscoveryListener implements NsdManager.DiscoveryListener {
         @Override
         public void onStartDiscoveryFailed(String serviceType, int errorCode) {}
diff --git a/thread/tests/multidevices/Android.bp b/thread/tests/multidevices/Android.bp
new file mode 100644
index 0000000..050caa8
--- /dev/null
+++ b/thread/tests/multidevices/Android.bp
@@ -0,0 +1,43 @@
+//
+// Copyright (C) 2024 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_team: "trendy_team_fwk_thread_network",
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+python_test_host {
+    name: "ThreadNetworkMultiDeviceTests",
+    main: "thread_network_multi_device_test.py",
+    srcs: ["thread_network_multi_device_test.py"],
+    test_config: "AndroidTest.xml",
+    libs: [
+        "mobly",
+    ],
+    test_options: {
+        unit_test: false,
+        tags: ["mobly"],
+    },
+    test_suites: [
+        "mts-tethering",
+        "general-tests",
+    ],
+    version: {
+        py3: {
+            embedded_launcher: true,
+        },
+    },
+}
diff --git a/thread/tests/multidevices/AndroidTest.xml b/thread/tests/multidevices/AndroidTest.xml
new file mode 100644
index 0000000..a2ea9aa
--- /dev/null
+++ b/thread/tests/multidevices/AndroidTest.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright (C) 2024 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.
+ -->
+
+<configuration description="Config for Thread Multi-device test cases">
+    <option name="config-descriptor:metadata" key="component" value="threadnetwork" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+    <option name="config-descriptor:metadata" key="mainline-param" value="com.google.android.tethering.apex" />
+
+    <object class="com.android.tradefed.testtype.suite.module.DeviceFeatureModuleController"
+        type="module_controller">
+        <option name="required-feature" value="android.hardware.thread_network" />
+    </object>
+    <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
+        <option name="mainline-module-package-name" value="com.google.android.tethering" />
+    </object>
+    <!--
+        Only run tests if the device under test is SDK version 34 (Android 14) or above.
+    -->
+    <object type="module_controller"
+        class="com.android.tradefed.testtype.suite.module.Sdk34ModuleController" />
+
+    <device name="device1">
+        <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" />
+    </device>
+    <device name="device2">
+        <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" />
+    </device>
+
+    <test class="com.android.tradefed.testtype.mobly.MoblyBinaryHostTest">
+      <!-- The mobly-par-file-name should match the module name -->
+      <option name="mobly-par-file-name" value="ThreadMultiDeviceTestCases" />
+      <!-- Timeout limit in milliseconds for all test cases of the python binary -->
+      <option name="mobly-test-timeout" value="180000" />
+    </test>
+</configuration>
diff --git a/thread/tests/multidevices/thread_network_multi_device_test.py b/thread/tests/multidevices/thread_network_multi_device_test.py
new file mode 100644
index 0000000..652576b
--- /dev/null
+++ b/thread/tests/multidevices/thread_network_multi_device_test.py
@@ -0,0 +1,67 @@
+#  Copyright (C) 2024 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.
+
+# Lint as: python3
+
+import logging
+import time
+
+from mobly import asserts
+from mobly import base_test
+from mobly import test_runner
+from mobly.controllers import android_device
+
+class ThreadNetworkMultiDeviceTest(base_test.BaseTestClass):
+    def setup_class(self):
+        self.node_a, self.node_b = self.register_controller(
+            android_device, min_number=2)
+        self.node_a.adb.shell([
+            'ot-ctl', 'factoryreset',
+        ])
+        self.node_b.adb.shell([
+            'ot-ctl', 'factoryreset',
+        ])
+        time.sleep(1)
+
+    def ot_ctl(self, node, cmd, expect_done=True):
+        args = cmd.split(' ')
+        args = ['ot-ctl'] + args
+        stdout = node.adb.shell(args).decode('utf-8')
+        if expect_done:
+            asserts.assert_in('Done', stdout)
+        return stdout
+
+    def test_b_should_be_able_to_discover_a(self):
+        self.ot_ctl(self.node_a, 'dataset init new')
+        self.ot_ctl(self.node_a, 'dataset commit active')
+        self.ot_ctl(self.node_a, 'ifconfig up')
+        self.ot_ctl(self.node_a, 'thread start')
+        self.ot_ctl(self.node_a, 'state leader')
+        stdout = self.ot_ctl(self.node_a, 'extaddr')
+        extaddr = stdout.splitlines()[0]
+        logging.info('node a extaddr: %s', extaddr)
+        asserts.assert_equal(len(extaddr), 16)
+
+        stdout = self.ot_ctl(self.node_b, 'scan')
+        asserts.assert_in(extaddr, stdout)
+        logging.info('discovered node a')
+
+
+if __name__ == '__main__':
+    # Take test args
+    if '--' in sys.argv:
+        index = sys.argv.index('--')
+        sys.argv = sys.argv[:1] + sys.argv[index + 1:]
+
+    test_runner.main()
diff --git a/thread/tests/unit/AndroidManifest.xml b/thread/tests/unit/AndroidManifest.xml
index ace7c52..8442e80 100644
--- a/thread/tests/unit/AndroidManifest.xml
+++ b/thread/tests/unit/AndroidManifest.xml
@@ -19,6 +19,8 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     package="android.net.thread.unittests">
 
+    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
+
     <application android:debuggable="true">
         <uses-library android:name="android.test.runner" />
     </application>
diff --git a/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java b/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java
index 493058f..52a9dd9 100644
--- a/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java
+++ b/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java
@@ -26,6 +26,7 @@
 import static android.net.thread.ThreadNetworkManager.PERMISSION_THREAD_NETWORK_PRIVILEGED;
 
 import static com.android.server.thread.ThreadNetworkCountryCode.DEFAULT_COUNTRY_CODE;
+import static com.android.server.thread.ThreadPersistentSettings.THREAD_ENABLED_IN_AIRPLANE_MODE;
 import static com.android.server.thread.openthread.IOtDaemon.ErrorCode.OT_ERROR_INVALID_STATE;
 
 import static com.google.common.io.BaseEncoding.base16;
@@ -35,6 +36,7 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.argThat;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.doAnswer;
@@ -64,6 +66,8 @@
 import android.os.RemoteException;
 import android.os.UserManager;
 import android.os.test.TestLooper;
+import android.provider.Settings;
+import android.util.AtomicFile;
 
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -75,7 +79,9 @@
 import com.android.server.thread.openthread.testing.FakeOtDaemon;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Captor;
@@ -133,7 +139,6 @@
     @Mock private TunInterfaceController mMockTunIfController;
     @Mock private ParcelFileDescriptor mMockTunFd;
     @Mock private InfraInterfaceController mMockInfraIfController;
-    @Mock private ThreadPersistentSettings mMockPersistentSettings;
     @Mock private NsdPublisher mMockNsdPublisher;
     @Mock private UserManager mMockUserManager;
     @Mock private IBinder mIBinder;
@@ -143,11 +148,15 @@
     private Context mContext;
     private TestLooper mTestLooper;
     private FakeOtDaemon mFakeOtDaemon;
+    private ThreadPersistentSettings mPersistentSettings;
     private ThreadNetworkControllerService mService;
     @Captor private ArgumentCaptor<ActiveOperationalDataset> mActiveDatasetCaptor;
 
+    @Rule(order = 1)
+    public final TemporaryFolder tempFolder = new TemporaryFolder();
+
     @Before
-    public void setUp() {
+    public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
 
         mContext = spy(ApplicationProvider.getApplicationContext());
@@ -164,10 +173,12 @@
         mFakeOtDaemon = new FakeOtDaemon(handler);
         when(mMockTunIfController.getTunFd()).thenReturn(mMockTunFd);
 
-        when(mMockPersistentSettings.get(any())).thenReturn(true);
         when(mMockUserManager.hasUserRestriction(eq(DISALLOW_THREAD_NETWORK))).thenReturn(false);
 
+        Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 0);
+
         when(mConnectivityResources.get()).thenReturn(mResources);
+        when(mResources.getBoolean(eq(R.bool.config_thread_default_enabled))).thenReturn(true);
         when(mResources.getString(eq(R.string.config_thread_vendor_name)))
                 .thenReturn(TEST_VENDOR_NAME);
         when(mResources.getString(eq(R.string.config_thread_vendor_oui)))
@@ -175,6 +186,10 @@
         when(mResources.getString(eq(R.string.config_thread_model_name)))
                 .thenReturn(TEST_MODEL_NAME);
 
+        final AtomicFile storageFile = new AtomicFile(tempFolder.newFile("thread_settings.xml"));
+        mPersistentSettings = new ThreadPersistentSettings(storageFile, mConnectivityResources);
+        mPersistentSettings.initialize();
+
         mService =
                 new ThreadNetworkControllerService(
                         mContext,
@@ -184,7 +199,7 @@
                         mMockConnectivityManager,
                         mMockTunIfController,
                         mMockInfraIfController,
-                        mMockPersistentSettings,
+                        mPersistentSettings,
                         mMockNsdPublisher,
                         mMockUserManager,
                         mConnectivityResources,
@@ -343,15 +358,9 @@
 
     @Test
     public void userRestriction_userBecomesRestricted_stateIsDisabledButNotPersisted() {
-        AtomicReference<BroadcastReceiver> receiverRef = new AtomicReference<>();
         when(mMockUserManager.hasUserRestriction(eq(DISALLOW_THREAD_NETWORK))).thenReturn(false);
-        doAnswer(
-                        invocation -> {
-                            receiverRef.set((BroadcastReceiver) invocation.getArguments()[0]);
-                            return null;
-                        })
-                .when(mContext)
-                .registerReceiver(any(BroadcastReceiver.class), any(), any(), any());
+        AtomicReference<BroadcastReceiver> receiverRef =
+                captureBroadcastReceiver(UserManager.ACTION_USER_RESTRICTIONS_CHANGED);
         mService.initialize();
         mTestLooper.dispatchAll();
 
@@ -360,21 +369,14 @@
         mTestLooper.dispatchAll();
 
         assertThat(mFakeOtDaemon.getEnabledState()).isEqualTo(STATE_DISABLED);
-        verify(mMockPersistentSettings, never())
-                .put(eq(ThreadPersistentSettings.THREAD_ENABLED.key), eq(false));
+        assertThat(mPersistentSettings.get(ThreadPersistentSettings.THREAD_ENABLED)).isTrue();
     }
 
     @Test
-    public void userRestriction_userBecomesNotRestricted_stateIsEnabledButNotPersisted() {
-        AtomicReference<BroadcastReceiver> receiverRef = new AtomicReference<>();
+    public void userRestriction_userBecomesNotRestricted_stateIsEnabled() {
         when(mMockUserManager.hasUserRestriction(eq(DISALLOW_THREAD_NETWORK))).thenReturn(true);
-        doAnswer(
-                        invocation -> {
-                            receiverRef.set((BroadcastReceiver) invocation.getArguments()[0]);
-                            return null;
-                        })
-                .when(mContext)
-                .registerReceiver(any(BroadcastReceiver.class), any(), any(), any());
+        AtomicReference<BroadcastReceiver> receiverRef =
+                captureBroadcastReceiver(UserManager.ACTION_USER_RESTRICTIONS_CHANGED);
         mService.initialize();
         mTestLooper.dispatchAll();
 
@@ -383,8 +385,6 @@
         mTestLooper.dispatchAll();
 
         assertThat(mFakeOtDaemon.getEnabledState()).isEqualTo(STATE_ENABLED);
-        verify(mMockPersistentSettings, never())
-                .put(eq(ThreadPersistentSettings.THREAD_ENABLED.key), eq(true));
     }
 
     @Test
@@ -401,6 +401,118 @@
         assertThat(failure.getErrorCode()).isEqualTo(ERROR_FAILED_PRECONDITION);
     }
 
+    @Test
+    public void airplaneMode_initWithAirplaneModeOn_otDaemonNotStarted() {
+        Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 1);
+
+        mService.initialize();
+        mTestLooper.dispatchAll();
+
+        assertThat(mFakeOtDaemon.isInitialized()).isFalse();
+    }
+
+    @Test
+    public void airplaneMode_initWithAirplaneModeOff_threadIsEnabled() {
+        Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 0);
+
+        mService.initialize();
+        mTestLooper.dispatchAll();
+
+        assertThat(mFakeOtDaemon.getEnabledState()).isEqualTo(STATE_ENABLED);
+    }
+
+    @Test
+    public void airplaneMode_changesFromOffToOn_stateIsDisabled() {
+        Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 0);
+        AtomicReference<BroadcastReceiver> receiverRef =
+                captureBroadcastReceiver(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+        mService.initialize();
+        mTestLooper.dispatchAll();
+
+        Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 1);
+        receiverRef.get().onReceive(mContext, new Intent());
+        mTestLooper.dispatchAll();
+
+        assertThat(mFakeOtDaemon.getEnabledState()).isEqualTo(STATE_DISABLED);
+    }
+
+    @Test
+    public void airplaneMode_changesFromOnToOff_stateIsEnabled() {
+        Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 1);
+        AtomicReference<BroadcastReceiver> receiverRef =
+                captureBroadcastReceiver(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+        mService.initialize();
+        mTestLooper.dispatchAll();
+
+        Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 0);
+        receiverRef.get().onReceive(mContext, new Intent());
+        mTestLooper.dispatchAll();
+
+        assertThat(mFakeOtDaemon.getEnabledState()).isEqualTo(STATE_ENABLED);
+    }
+
+    @Test
+    public void airplaneMode_setEnabledWhenAirplaneModeIsOn_WillNotAutoDisableSecondTime() {
+        Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 1);
+        AtomicReference<BroadcastReceiver> receiverRef =
+                captureBroadcastReceiver(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+        CompletableFuture<Void> setEnabledFuture = new CompletableFuture<>();
+        mService.initialize();
+
+        mService.setEnabled(true, newOperationReceiver(setEnabledFuture));
+        mTestLooper.dispatchAll();
+        Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 0);
+        receiverRef.get().onReceive(mContext, new Intent());
+        mTestLooper.dispatchAll();
+        Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 1);
+        receiverRef.get().onReceive(mContext, new Intent());
+        mTestLooper.dispatchAll();
+
+        assertThat(mFakeOtDaemon.getEnabledState()).isEqualTo(STATE_ENABLED);
+        assertThat(mPersistentSettings.get(THREAD_ENABLED_IN_AIRPLANE_MODE)).isTrue();
+    }
+
+    @Test
+    public void airplaneMode_setDisabledWhenAirplaneModeIsOn_WillAutoDisableSecondTime() {
+        Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 1);
+        AtomicReference<BroadcastReceiver> receiverRef =
+                captureBroadcastReceiver(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+        CompletableFuture<Void> setEnabledFuture = new CompletableFuture<>();
+        mService.initialize();
+        mService.setEnabled(true, newOperationReceiver(setEnabledFuture));
+        mTestLooper.dispatchAll();
+
+        mService.setEnabled(false, newOperationReceiver(setEnabledFuture));
+        mTestLooper.dispatchAll();
+        Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 0);
+        receiverRef.get().onReceive(mContext, new Intent());
+        mTestLooper.dispatchAll();
+        Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 1);
+        receiverRef.get().onReceive(mContext, new Intent());
+        mTestLooper.dispatchAll();
+
+        assertThat(mFakeOtDaemon.getEnabledState()).isEqualTo(STATE_DISABLED);
+        assertThat(mPersistentSettings.get(THREAD_ENABLED_IN_AIRPLANE_MODE)).isFalse();
+    }
+
+    private AtomicReference<BroadcastReceiver> captureBroadcastReceiver(String action) {
+        AtomicReference<BroadcastReceiver> receiverRef = new AtomicReference<>();
+
+        doAnswer(
+                        invocation -> {
+                            receiverRef.set((BroadcastReceiver) invocation.getArguments()[0]);
+                            return null;
+                        })
+                .when(mContext)
+                .registerReceiver(
+                        any(BroadcastReceiver.class),
+                        argThat(actualIntentFilter -> actualIntentFilter.hasAction(action)),
+                        any(),
+                        any());
+
+        return receiverRef;
+    }
+
     private static IOperationReceiver newOperationReceiver(CompletableFuture<Void> future) {
         return new IOperationReceiver.Stub() {
             @Override
diff --git a/tools/aospify_device.sh b/tools/aospify_device.sh
new file mode 100755
index 0000000..f25ac9d
--- /dev/null
+++ b/tools/aospify_device.sh
@@ -0,0 +1,164 @@
+#!/bin/bash
+
+# Script to swap core networking modules in a GMS userdebug device to AOSP modules, by remounting
+# the system partition and replacing module prebuilts. This is only to be used for local testing,
+# and should only be used on userdebug devices that support "adb root" and remounting the system
+# partition using overlayfs.
+#
+# Usage: aospify_device.sh [device_serial]
+# Reset by wiping data (adb reboot bootloader && fastboot erase userdata && fastboot reboot).
+#
+# This applies to NetworkStack, CaptivePortalLogin, dnsresolver, tethering, cellbroadcast modules,
+# which generally need to be preloaded together (core networking modules + cellbroadcast which
+# shares its certificates with NetworkStack and CaptivePortalLogin)
+#
+# This allows device manufacturers to test their changes in AOSP modules, running them on their
+# own device builds, before contributing contributing the patches to AOSP. After running this script
+# once AOSP modules can be quickly built and updated on the prepared device with:
+#   m NetworkStack
+#   adb install --staged $ANDROID_PRODUCT_OUT/system/priv-app/NetworkStack/NetworkStack.apk \
+#   adb reboot
+# or for APEX modules:
+#   m com.android.tethering deapexer
+#   $ANDROID_HOST_OUT/bin/deapexer decompress --input $ANDROID_PRODUCT_OUT/system/apex/com.android.tethering.capex --output /tmp/decompressed.apex
+#   adb install /tmp/decompressed.apex && adb reboot
+#
+# This has been tested on Android T and Android U Pixel devices. On recent (U+) devices, it requires
+# setting a released target SDK (for example target_sdk_version: "34") in
+# packages/modules/Connectivity/service/ServiceConnectivityResources/Android.bp before building.
+set -e
+
+function push_apex {
+    local original_apex_name=$1
+    local aosp_apex_name=$2
+    if $ADB_CMD shell ls /system/apex/$original_apex_name.capex 1>/dev/null 2>/dev/null; then
+        $ADB_CMD shell rm /system/apex/$original_apex_name.capex
+        $ADB_CMD push $ANDROID_PRODUCT_OUT/system/apex/$aosp_apex_name.capex /system/apex/
+    else
+        rm -f /tmp/decompressed_$aosp_apex_name.apex
+        $ANDROID_HOST_OUT/bin/deapexer decompress --input $ANDROID_PRODUCT_OUT/system/apex/$aosp_apex_name.capex --output /tmp/decompressed_$aosp_apex_name.apex
+        $ADB_CMD shell rm /system/apex/$original_apex_name.apex
+        $ADB_CMD push /tmp/decompressed_$aosp_apex_name.apex /system/apex/$aosp_apex_name.apex
+        rm /tmp/decompressed_$aosp_apex_name.apex
+    fi
+}
+
+function push_apk {
+    local app_type=$1
+    local original_apk_name=$2
+    local aosp_apk_name=$3
+    $ADB_CMD shell rm /system/$app_type/$original_apk_name/$original_apk_name.apk
+    $ADB_CMD push $ANDROID_PRODUCT_OUT/system/$app_type/$aosp_apk_name/$aosp_apk_name.apk /system/$app_type/$original_apk_name/
+}
+
+NETWORKSTACK_AOSP_SEPOLICY_KEY="<signer signature=\"308205dc308203c4a003020102020900fc6cb0d8a6fdd16\
+8300d06092a864886f70d01010b0500308181310b30090603550406130255533113301106035504080c0a43616c69666f72\
+6e69613116301406035504070c0d4d6f756e7461696e20566965773110300e060355040a0c07416e64726f69643110300e0\
+60355040b0c07416e64726f69643121301f06035504030c18636f6d2e616e64726f69642e6e6574776f726b737461636b30\
+20170d3139303231323031343632305a180f34373537303130383031343632305a308181310b30090603550406130255533\
+113301106035504080c0a43616c69666f726e69613116301406035504070c0d4d6f756e7461696e20566965773110300e06\
+0355040a0c07416e64726f69643110300e060355040b0c07416e64726f69643121301f06035504030c18636f6d2e616e647\
+26f69642e6e6574776f726b737461636b30820222300d06092a864886f70d01010105000382020f003082020a0282020100\
+bb71f5137ff0b2d757acc2ca3d378e0f8de11090d5caf3d49e314d35c283b778b02d792d8eba440364ca970985441660f0b\
+c00afbc63dd611b1bf51ad28a1edd21e0048f548b80f8bd113e25682822f57dab8273afaf12c64d19a0c6be238f3e66ddc7\
+9b10fd926931e3ee60a7bf618644da3c2c4fc428139d45d27beda7fe45e30075b493ead6ec01cdd55d931c0a657e2e59742\
+ca632b6dc3842a2deb7d22443c809291d7a549203ae6ae356582a4ca23f30f0549c4ec8408a75278e95c69e8390ad5280bc\
+efaef6f1309a41bd9f3bfb5d12dca7e79ec6fd6848193fa9ab728224887b4f93e985ec7cbf6401b0e863a4b91c05d046f04\
+0fe954004b1645954fcb4114cee1e8b64b47d719a19ef4c001cb183f7f3e166e43f56d68047c3440da34fdf529d44274b8b\
+2f6afb345091ad8ad4b93bd5c55d52286a5d3c157465db8ddf62e7cdb6b10fb18888046afdd263ae6f2125d9065759c7e42\
+f8610a6746edbdc547d4301612eeec3c3cbd124dececc8d38b20e73b13f24ee7ca13a98c5f61f0c81b07d2b519749bc2bcb\
+9e0949aef6c118a3e8125e6ab57fce46bb091a66740e10b31c740b891900c0ecda9cc69ecb4f3369998b175106dd0a4ffd7\
+024eb7e75fedd1a5b131d0bb2b40c63491e3cf86b8957b21521b3a96ed1376a51a6ac697866b0256dee1bcd9ab9a188bf4c\
+ed80b59a5f24c2da9a55eb7b0e502116e30203010001a3533051301d0603551d0e041604149383c92cfbf099d5c47b0c365\
+7d8622a084b72e1301f0603551d230418301680149383c92cfbf099d5c47b0c3657d8622a084b72e1300f0603551d130101\
+ff040530030101ff300d06092a864886f70d01010b050003820201006a0501382fde2a6b8f70c60cd1b8ee4f788718c288b\
+170258ef3a96230b65005650d6a4c42a59a97b2ddec502413e7b438fbd060363d74b74a232382a7f77fd3da34e38f79fad0\
+35a8b472c5cff365818a0118d87fa1e31cc7ed4befd27628760c290980c3cc3b7ff0cfd01b75ff1fcc83e981b5b25a54d85\
+b68a80424ac26015fb3a4c754969a71174c0bc283f6c88191dced609e245f5938ffd0ad799198e2d0bf6342221c1b0a5d33\
+2ed2fffc668982cabbcb7d3b630ff8476e5c84ac0ad37adf9224035200039f95ec1fa95bf83796c0e8986135cee2dcaef19\
+0b249855a7e7397d4a0bf17ea63d978589c6b48118a381fffbd790c44d80233e2e35292a3b5533ca3f2cc173f85cf904adf\
+e2e4e2183dc1eba0ebae07b839a81ff1bc92e292550957c8599af21e9c0497b9234ce345f3f508b1cc872aa55ddb5e773c5\
+c7dd6577b9a8b6daed20ae1ff4b8206fd9f5c8f5a22ba1980bef01ae6fcb2659b97ad5b985fa81c019ffe008ddd9c8130c0\
+6fc6032b2149c2209fc438a7e8c3b20ce03650ad31c4ee48f169777a0ae182b72ca31b81540f61f167d8d7adf4f6bb2330f\
+f5c24037245000d8172c12ab5d5aa5890b8b12db0f0e7296264eb66e7f9714c31004649fb4b864005f9c43c80db3f6de52f\
+d44d6e2036bfe7f5807156ed5ab591d06fd6bb93ba4334ea2739af8b41ed2686454e60b666d10738bb7ba88001\">\
+<seinfo value=\"network_stack\"\/><\/signer>"
+
+DEVICE=$1
+ADB_CMD="adb -s $DEVICE"
+
+if [ -z "$DEVICE" ]; then
+    echo "Usage: aospify_device.sh [device_serial]"
+    exit 1
+fi
+
+if [ -z "$ANDROID_BUILD_TOP" ]; then
+    echo "Run build/envsetup.sh first to set ANDROID_BUILD_TOP"
+    exit 1
+fi
+
+if ! $ADB_CMD wait-for-device shell pm path com.google.android.networkstack; then
+    echo "This device is already not using GMS modules"
+    exit 1
+fi
+
+read -p "This script is only for test purposes and highly likely to make your device unusable. \
+Continue ? <y/N>" prompt
+if [[ $prompt != "y" ]]
+then
+    exit 0
+fi
+
+cd $ANDROID_BUILD_TOP
+source build/envsetup.sh
+lunch aosp_arm64-trunk_staging-userdebug
+m NetworkStack CaptivePortalLogin com.android.tethering com.android.cellbroadcast \
+    com.android.resolv deapexer \
+    out/target/product/generic_arm64/system/etc/selinux/plat_mac_permissions.xml \
+    out/target/product/generic_arm64/system/etc/permissions/com.android.networkstack.xml
+
+$ADB_CMD root
+$ADB_CMD remount
+$ADB_CMD reboot
+
+echo "Waiting for boot..."
+$ADB_CMD wait-for-device;
+until [[ $($ADB_CMD shell getprop sys.boot_completed) == 1 ]]; do
+    sleep 1;
+done
+
+$ADB_CMD root
+$ADB_CMD remount
+
+push_apk priv-app NetworkStackGoogle NetworkStack
+push_apk app CaptivePortalLoginGoogle CaptivePortalLogin
+push_apex com.google.android.tethering com.android.tethering
+push_apex com.google.android.cellbroadcast com.android.cellbroadcast
+push_apex com.google.android.resolv com.android.resolv
+
+# Replace the network_stack key used to set its sepolicy context
+rm -f /tmp/pulled_plat_mac_permissions.xml
+$ADB_CMD pull /system/etc/selinux/plat_mac_permissions.xml /tmp/pulled_plat_mac_permissions.xml
+sed_replace='s/<signer signature="[0-9a-fA-F]+"><seinfo value="network_stack"\/><\/signer>/'$NETWORKSTACK_AOSP_SEPOLICY_KEY'/'
+sed -E "$sed_replace" /tmp/pulled_plat_mac_permissions.xml |
+    $ADB_CMD shell 'cat > /system/etc/selinux/plat_mac_permissions.xml'
+rm /tmp/pulled_plat_mac_permissions.xml
+
+# Update the networkstack privapp-permissions allowlist
+rm -f /tmp/pulled_privapp-permissions.xml
+$ADB_CMD pull /system/etc/permissions/privapp-permissions-google.xml /tmp/pulled_privapp-permissions.xml
+
+# Remove last </permission> line, and the permissions for com.google.android.networkstack
+sed -nE '1,/<\/permissions>/p' /tmp/pulled_privapp-permissions.xml \
+    | sed -E '/com.google.android.networkstack/,/privapp-permissions/d' > /tmp/modified_privapp-permissions.xml
+# Add the AOSP permissions and re-add the </permissions> line
+sed -nE '/com.android.networkstack/,/privapp-permissions/p' $ANDROID_PRODUCT_OUT/system/etc/permissions/com.android.networkstack.xml \
+    >> /tmp/modified_privapp-permissions.xml
+echo '</permissions>' >> /tmp/modified_privapp-permissions.xml
+
+$ADB_CMD push /tmp/modified_privapp-permissions.xml /system/etc/permissions/privapp-permissions-google.xml
+
+rm /tmp/pulled_privapp-permissions.xml /tmp/modified_privapp-permissions.xml
+
+echo "Done modifying, rebooting"
+$ADB_CMD reboot
\ No newline at end of file